Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Custom user fields on every page

Background: I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.

As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.

It is easy to show a user name through using

HttpContext.Current.User.Identity.Name

What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.

(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)

Question

How do you solve this:

  1. Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
  2. Without having to roundtrip the database every page request for these details?
  3. Without querying the database if a user is not logged in?
  4. When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.

I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?

TLDR;

I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?

like image 688
Erik J. Avatar asked Mar 14 '23 23:03

Erik J.


1 Answers

The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.

On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
    // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

   // Add custom user claims here
   userIdentity.AddClaims(new[]
   {
       new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
       new Claim("MyApp:LastName",this.LastName),
   });

   return userIdentity;
}

This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.

After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.

To get the data out of claims I usually do extension methods on IPrincipal

public static String GetFirstName(this IPrincipal principal)
{
    var claimsPrincipal = principal as ClaimsPrincipal;
    if (claimsPrincipal == null)
    {
        throw new DomainException("User is not authenticated");
    }

    var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
    if (personNameClaim != null)
    {
        return personNameClaim.Value;
    }

    return String.Empty;
}

This way you can access your claims data from your Razor views: User.GetFirstName()

And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.

The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.

like image 169
trailmax Avatar answered Mar 24 '23 05:03

trailmax