Search code examples
c#asp.net-mvcasp.net-identity-2

How to extend the ApplicationUser in ASP.NET MVC correctly?


This is my first attempt to use the ASP.NET identity with the builtin authentication. All previous attempts resulted in having a manual check for user credentials and then setting FormsAuthentication AuthCookie. But I had some problems with a signalR connection not having authentication information this way. So I started from scratch with the builtin authentication.

So I extended my ApplicationUser object with some fields:

public class ApplicationUser : IdentityUser
{
    public long SteamId { get; set; }
    public string Avatar { get; internal set; }
    public string Name { get; internal set; }
    public int Credits { get; internal set; }
    public long? DiscordId { get; internal set; }
    public DateTime? LastLogin { get; internal set; }
    public DateTime RegisterDate { get; internal set; }
}

These fields will create new columns in my AspNetUsers table. The problem is, I can't access these values in my views. For that I need to use claims if I understand correctly. These claims are stored in another table called AspNetUserClaims. So I have to add those claims to the user

await UserManager.AddClaimAsync(user.Id, new Claim("Avatar", user.Avatar));

and creating a extension method to get the avatar from the principal

public static class ClaimsPrincipalExtension
{
    public static string GetAvatar(this ClaimsPrincipal principal)
    {
        var avatar = principal.Claims.FirstOrDefault(c => c.Type == "Avatar");
        return avatar?.Value;
    }
}

Now I can access the avatar in my view

@(((ClaimsPrincipal)User).GetAvatar())

I don't think that's a really good and clean way to do this, but this is the first time I am using it, so I don't know what's the best practices to do. There are three main reasons why I don't like it:

  1. The avatar is stored twice, once in the AspNetUsers table as column and once as a new entry in AspNetUserClaims
  2. Fields like SteamId, Credits or RegisterDate are saved as string in the AspNetUserClaims table, and I have to convert them to int, long or DateTime in the extension method
  3. I have to write an extension method for every property I'll add to the ApplicationUser

What is the best way to handle additional fields?

  • Might it be an option to create a new object called AdditionalUserInformation and store the json serialized string as claim and just have one extension method to return the object? Then the properties would have the correct type.
  • Or is there a way to access the properties of the ApplicationUser in the view?
  • Or is the problem the use of those in the view? Should I access them in the controller and create a Model for the view containing all information? What about a _Layout page or a _navigation partial view then? They also might need the information of the user. I think I can't feed them with the controllers.

I also checked some examples. Mostly they add those extension methods and mostly just string properties.

Examples

I am currently kinda stuck here on finding the best and maybe clean solution.


Solution

  • You can access AspNetUsers table with the name "Users" from your DbContext. First query AspNetUsers with current user's username or userId, then fill your ViewModel and send it to the view. The following code shows what I described:

    [Authorize]
    public ActionResult UserTopNavBar()
    {
         var userTopNavBarViewModel = new UserTopNavBarViewModel();
         using(ApplicationDbContext _db = new ApplicationDbContext())
         {
             var user = _db.Users.FirstOrDefault(a => a.UserName == User.Identity.Name);
             if (user != null)
             {
                 userTopNavBarViewModel.Name = user.FirstName + " " + user.LastName;
                 userTopNavBarViewModel.Picture = user.Picture;
                 userTopNavBarViewModel.Id = user.Id;
             }
         }
         return PartialView(userTopNavBarViewModel);
    }
    

    And this is my ViewModel

    public class UserTopNavBarViewModel
    {
         public string Name { get; set; }
         public byte[] Picture { get; set; }
         public string Id { get; set; }
    }