Search code examples
asp.net-identityasp.net-coreasp.net-core-mvcasp.net-identity-3

How to access a session in in the _Layout without worrying it has expired


I am using ASP.NET Core & Identity 3.

When I log in I read the current select user's UI template and in my _Layout.cshml file I load the css based off this template.

The user can change his theme and I store it in a session variable via the controller

public IActionResult ChangeTheme(int id, string returnUrl)
{
    HttpContext.Session.SetInt32("Template", (id));
    return Redirect(returnUrl);
}

Instead of querying the database with every cshtml load I put the Template in a Session variable and in my Layout.cshtml I render a different css depending on the template

        switch (template)
        {
            case (int)TemplateEnum.Template2:
                <text>
                <link rel="stylesheet" href="~/css/template1.css" />
                </text>
                break;
            case (int)TemplateEnum.Template2:
                <text>
                <link rel="stylesheet" href="~/css/template2.css" />
                </text>
                break;
        {

I'm wondering what happens if the session expires.

  1. Taking into account I access the value in my _Layout.cshtmlis there anyway to catch it if it becomes null and immediately load it from the db before a new page is rendered.

  2. Since I use Identity 3, is Claims maybe a better option? I haven't used it before. What would the code be for my example above

  3. Another option that is better for my scenario?


Solution

  • Instead of querying the database with every cshtml load I put the Template in a Session variable and in my Layout.cshtml I render a different css depending on the template

    If hitting the database is your only concern and you have abstracted your repository (or user store, if you store it on the identity type), you can use the decorator pattern to implement local caching.

    public interface IUserRepository
    {
        string GetUserTheme(int userId);
        void SetUserTheme(int userId, string theme);
    }
    
    public class CachedUserRepository : IUserRepository
    {
        private readonly IMemoryCache cache;
        private readonly IUserRepository userRepository;
        // Cache Expire duration
        private static TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
    
        public CachedUserRepository(IUserRepository userRepository, IMemoryCache memoryCache)
        {
            if (userRepository == null)
                throw new ArgumentNullException(nameof(userRepository));
    
            if (memoryCache == null)
                throw new ArgumentNullException(nameof(memoryCache));
    
            this.userRepository = userRepository;
            this.cache = memoryCache;
        }
    
        public string GetUserTheme(int userId)
        {
            string theme;
    
            // adding a prefix to make the key unique
            if (cache.TryGetValue($"usertheme-{userId}", out theme))
            {
                // found in cache
                return theme;
            };
    
            // fetch from database
            theme = userRepository.GetUserTheme(userId);
    
            // put it into the cache, expires in 5 minutes
            cache.Set($"usertheme-{userId}", theme, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = CacheDuration });
    
            return theme;
        }
    
        public void SetUserTheme(int userId, string theme)
        {
            // persist it
            userRepository.SetUserTheme(userId, theme);
    
    
            // put it into the cache, expires in 5 minutes
            cache.Set($"usertheme-{userId}", theme, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = CacheDuration });
        }
    }
    

    The catch is, there is no built-in support for decorators in the default ASP.NET Core DI system. You'd have to use a 3rd party IoC container (Autofac, StructureMap etc.).

    You could of course register it like this

        services.AddScoped<IUserRepository>(container => {
            return new CachedUserRepository(container.GetService<UserRepository>(), container.GetServices<IMemoryCache>());
        });
    

    but that's a bit cumbersome. Otherwise store it in a long-lived cookie, it has the advantage that the theme will still be active when the user is not logged in and you can set the cookie, when the user logs in.