1

I am very new to identity and MVC, I am trying to create an MVC app as one of my first projects.

I have been able to follow a few tutorials and have successfully added additional properties to my ApplicationUser : IdentityUser class

public class ApplicationUser
    : IdentityUser<string, ApplicationUserLogin,
    ApplicationUserRole, ApplicationUserClaim>
{
    [Required]
    [Display(Name = "UserName")]
    [StringLength(50)]
    public string Handle { get; set; }

    [StringLength(100, ErrorMessage = "Your {0} can be at most {1} characters long.")]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [StringLength(100, ErrorMessage = "Your {0} can be at most {1} characters long.")]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Required]
    [Display(Name = "User Creation Date")]
    public DateTime UserCreationDate { get; set; }

    public ApplicationUser()
    {
        this.Id = Guid.NewGuid().ToString();

        // Add any custom User properties/code here
    }

My questions are:

  1. I see where Email is set to require a unique Email in App_Start.IdentityConfig.cs, Is there a way to set it up to require a unique custom property like Handle?

        var manager = new ApplicationUserManager(new UserStore<ApplicationUser, ApplicationRole, string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(context.Get<ApplicationDbContext>()));
        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
    
  2. In the Partial View of Views.Shared._LoginPartial.cshtml it shows the UserName/Email of the person logging into the application using User.Identity.GetUserName() is there a way to reference one of my custom properties there instead such as FirstName, or Handle?

    @using Microsoft.AspNet.Identity
    
    @if (Request.IsAuthenticated)
    {
        using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
        {
        @Html.AntiForgeryToken()
    
        <ul class="nav navbar-nav navbar-right">
            <li>
                @Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
            </li>
            <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
        </ul>
        }
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
            <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
        </ul>
    }
    
shaun
  • 1,223
  • 1
  • 19
  • 44

2 Answers2

2

Implementing a custom UserValidator has already been covered here: How can customize Asp.net Identity 2 username already taken validation message?

Rather than hitting the database for every page request to display the handle, add a claim during signin.

Define your own claim:

public static class CustomClaimTypes
{
    public const string Handle = "http://schemas.xmlsoap.org/ws/2014/03/mystuff/claims/handle";
}

During signin, set the claim:

private async Task SignInAsync(ApplicationUser user, bool isPersistent, string password = null)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    //Get the handle and add the claim to the identity.
    var handle = GetTheHandle();
    identity.AddClaim(new Claim(CustomClaimTypes.Handle, handle);

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

Then, via an extension method you can read it out the same way as GetUserName():

public static class IdentityExtensions
{
    public static string GetHandle(this IIdentity identity)
    {
        if (identity == null)
            return null;

        return (identity as ClaimsIdentity).FirstOrNull(CustomClaimTypes.Handle);
    }

    internal static string FirstOrNull(this ClaimsIdentity identity, string claimType)
    {
        var val = identity.FindFirst(claimType);

        return val == null ? null : val.Value;
    }
}

Finally, in your view:

@User.Identity.GetHandle()
Community
  • 1
  • 1
Brendan Green
  • 11,676
  • 5
  • 44
  • 76
  • Why are you telling me to set Handle to a URL? I was wanting it to be my custom UserName field setup in my ApplicationUser Class `[Required] [Display(Name = "UserName")] [StringLength(50)] public string Handle { get; set; }` I did this to get around UserName/Email linked in Identity 2.0, Every time I tried to break this link I would get `Invalid login attempt.` – shaun Jun 01 '15 at 16:41
  • Also I tried implementing the Stack Overflow article mentioned for my first question. [How can customize Asp.net Identity 2 username already taken validation message?](http://stackoverflow.com/questions/27655578/how-can-customize-asp-net-identity-2-username-already-taken-validation-message). I mentions Resources. When I implemented I get `'Microsoft.AspNet.Identity.Resources' is inaccessible due to its protection level` I understand since Microsoft.AspNet.Identity.Resources is marked as protected internal instead of Public, SO I REALLY don't understand how he was able to use it in his code. – shaun Jun 01 '15 at 17:00
  • @shaun just to clarify, this is not setting the handle to a URL. Rather, the URL is the claim type identifier, and if you look closely at the `SignInAsync` method, we are creating a `Claim` instance that uses the URL as the claim type. See standard claim types here: https://msdn.microsoft.com/en-us/library/system.security.claims.claimtypes%28v=vs.110%29.aspx – Brendan Green Jun 03 '15 at 03:32
-1

1) I would make your own UserValidator inherit from microsoft's UserValidator and handle your extra field.

Updated:

2) You can make your own extension method to get the extra field you added.

public static string GetSomething(this IIdentity identity)
{
    if (identity.IsAuthenticated)
    {
        var userStore = new UserStore<ApplicationUser>(new Context());
        var manager = new UserManager<ApplicationUser>(userStore);
        var currentUser = manager.FindById(identity.GetUserId());
        return something = currentUser.something;                
    }
    return null;
}
imGreg
  • 900
  • 9
  • 24
  • 1
    `IIdentity` class does not cast to `ApplicationUser`. This will throw runtime exception. – trailmax May 31 '15 at 14:29
  • that is what I am getting as well. `Unable to cast object of type 'System.Security.Claims.ClaimsIdentity' to type 'Foo.Models.ApplicationUser'`. ApplicationUser is of Interface IUser not IIdentity. I am not seeing anyway to link the two as of right now. – shaun May 31 '15 at 15:05