5

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?

7
  • I know you can do it for sure. Checkout this: stackoverflow.com/questions/8369055/… Commented Aug 26, 2015 at 22:04
  • 1
    You can use a custom IPrincipal and add additional values to your FormsAuthenticationTicket when the user logs in, so they can be read from the cookie in every request rather than calling the database (although adding a image to the cookie may not be appropriate). I don't use Identity but I believe it allow you to do this using Claims. Commented Aug 27, 2015 at 0:22
  • itorian.com/2013/11/customize-users-profile-in-aspnet.html Commented Aug 27, 2015 at 5:31
  • Will every page be the same, if so you can setup the look and feel in _Layout. In the _Layout define a Div which should contain your views, and use Ajax to load the PartialView content. This way you only load the profile details once Commented Aug 27, 2015 at 6:13
  • These only explain how to add new fields to the user object - this is not a problem at all (I have this working) - All I want is to grab this info once per user session rather than having to query the database all the time. The PartialView solution is not going to fly as we're not using a frontend mvvm framework - every time a page is loaded, it is recreated / reloaded. Commented Aug 27, 2015 at 8:47

4 Answers 4

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I ended up using this approach, thanks for the heads up with updated values (forgot about that) :)
2

You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:

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("MyValueName1","value1"),
       new Claim("MyValueName2","value2"),
       new Claim("MyValueName2","value3"),
       // and so on
   });

   return userIdentity;
}

And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.

public ActionResult MyAction()
{
     // you have access the authenticated user's claims 
     // simply by casting User.Identity to ClaimsIdentity
     var claims = ((ClaimsIdentity)User.Identity).Claims;
     // or 
     var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
} 

1 Comment

I hadn't thought of that - that's a good way to implement this - I'll check this out!
1

I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)

public interface IApplicationContext
{
    //Interface
    string GetUsername();
}

public class ApplicationContext : IApplicationContext
{
    public static IApplicationContext Current
    {
        get
        {
            return DependencyResolver.Current.GetService<IApplicationContext>();
        }
    }


    //appropriate functions to get required data
    public string GetUsername() {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            return HttpContext.Current.User.Identity.Name;
        }
        return null;
    }
}

Then you just reference the "Current" property in your view directly.

@ApplicationContext.Current.GetUsername()

This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.

2 Comments

This is actually the way I'm doing this right now as it seems the most logical way. I'm just trying to reduce the amount of queries as this system might have to support over a few hundred concurrent users, 100 queries every few seconds does make an impact.
That is a pretty bad solution - a lot of antipatterns here: from the view reach down to service via static accessor that is using service-locator. That defies the purpose of using MVC.
0

Simply implement ChildAction with caching and vary by loggedin user

4 Comments

His 4th requirement says no caching. Storing in cookies with a child action would probably still work though.
caching doesn't mean automatically you need to rely on SESSION data, right?
The way I see it is to make use of Server caching and Vary by user and possible a token from db which indicates profile information is changed. Using cookies is not needed in the most simple case and is probably not the most effectient strategy anyway.
Sorry for the late response but in his 4th acceptance criteria he explicitly says that he needs to be able to handle scaling up across multiple sites. So he can't use server caching since the user may not be guaranteed to be returned to the same server. He also says that he's not interested in SQL caching or Redis (x-site caching solution). I said cookies could work b/c you could send a set of claims to the cookie which is submitted with each request. Then the ChildAction can use the claims from the cookie to build the response.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.