0

I'm writing a class that will be available as a service, which provides some information about the current user.

public class UserContextService
{
    private readonly ApplicationDbContext DbContext;
    private readonly IHttpContextAccessor ContextAccessor;
    private readonly UserManager<ApplicationUser> UserManager;

    public UserContextService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, IHttpContextAccessor contextAccessor)
    {
        DbContext = dbContext;
        UserManager = userManager;
        ContextAccessor = contextAccessor;
    }

    public async Task<UserContextModel> GetUserContext()
    {
        if (UserContextModel == null)
        {
            ClaimsPrincipal? principal = ContextAccessor.HttpContext?.User;
            if (principal == null)
                throw new InvalidOperationException("No ClaimsPrincipal found for user.");

            ApplicationUser user = await UserManager.GetUserAsync(principal);
            UserContextModel = await DbContext.Facilities
                .Where(f => f.Id == user.FacilityId)
                .Select(f => new UserContextModel
                {
                    Name = user.Name,
                    FacilityId = f.Id,
                    FacilityName = f.Name,
                })
                .FirstOrDefaultAsync();
        }
        Debug.Assert(UserContextModel != null);
        return UserContextModel;
    }
    private UserContextModel? UserContextModel = null;
}

This works well. But it may be called for nearly all pages so I want to make it as efficient as possible.

Is there any way to optimize GetUserContext()? In particular, I'm using UserManager.GetUserAsync() to get the user, and then I'm doing a query to get information about the facility. That could easily be combined into a single query with a join. And that seems like it would be more efficient.

But I would need the user ID to make that single query. And I don't see another way to obtain the ID of the current user without retrieving the current ApplicationUser record.

Any tips for optimizing this?

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • How many users in your table? How often does it change? – Nick.Mc Jan 26 '22 at 01:54
  • In the past I've made one call to the DB and populated claims from that - effectively cached the information. This solution is fine if the claim rarely changes – Nick.Mc Jan 26 '22 at 01:55
  • @Nick.McDermaid: Initially, there will only be a handful of users and it won't change often. But I can't say how many users I may have in the future. – Jonathan Wood Jan 26 '22 at 01:56
  • @Nick.McDermaid: Well, this class registered with the dependency injector using `AddScoped()`, so it would still be created for every request. – Jonathan Wood Jan 26 '22 at 01:57
  • For less than 20,000 or so I really don't think you need to worry from the database perspective. If it's called for every request, it's up to you whether the info it returns comes from a database or from some cached data store. – Nick.Mc Jan 26 '22 at 01:58
  • @Nick.McDermaid: Well, do you mean like a static variable? The obvious problem then would be when it changes. – Jonathan Wood Jan 26 '22 at 01:59
  • I can only tell you what I did in my MVC app about five years ago: in the "Login" pipeline look up all additional user info in the database and add it to the security claim object. Then after that, refer to the security claim object not the database. The security info is then refreshed from the database on every login – Nick.Mc Jan 26 '22 at 02:04
  • The most efficient path forward is to fetch this data when the user is authenticated and then store it for the entire session. Are you using token-based authentication? Session-based? – John Glenn Jan 26 '22 at 02:05
  • @JohnGlenn: I'm using the default, out-of-the-box authentication that comes with ASP.NET Core. Not sure how I'd detect when a user is authenticated. I guess I could modify the login page and store it in a static variable. – Jonathan Wood Jan 26 '22 at 02:08
  • This is my question from way back containing code that loads additional data when logging in - it may no longer be valid though https://stackoverflow.com/questions/43343399/capturing-login-event-so-i-can-cache-other-user-information – Nick.Mc Jan 26 '22 at 02:08
  • 1
    Since you are using the built-in Identity library in .NET Core, you could easily [add this information to your user model / claim][1] so that it is accessible for any authenticated user without having to constantly run multiple queries against your database for every request to retrieve the same user data. https://learn.microsoft.com/en-us/aspnet/core/security/authentication/add-user-data?view=aspnetcore-6.0&tabs=visual-studio#add-claims-to-identity-using-iuserclaimsprincipalfactoryapplicationuser – John Glenn Jan 26 '22 at 05:31
  • @JohnGlenn: Thanks. If I understand, you're saying to add the needed properties to my `ApplicationUser` class. In fact, I am already doing this. The problem is that my `ApplicationUser` class has a foreign key to another table, and I need some column values from that table as well. – Jonathan Wood Jan 27 '22 at 01:38
  • Then go look up those values from the other table as well and add to the class. If you are already doing it it shouldn't be too hard. – Nick.Mc Feb 06 '22 at 02:33

0 Answers0