6

In MVC 5 you could assign a value to session in global.asx when the session started. Is there a way you can do this in .Net Core MVC? I have session configured but in the middleware it seems to get called on every request.

doogdeb
  • 400
  • 3
  • 8

3 Answers3

5

nercan's solution will work, but I think I found a solution that requires less code and may have other advantages.

First, wrap DistributedSessionStore like this:

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Session;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

public interface IStartSession
{
    void StartSession(ISession session);
}

public class DistributedSessionStoreWithStart : ISessionStore
{
    DistributedSessionStore innerStore;
    IStartSession startSession;
    public DistributedSessionStoreWithStart(IDistributedCache cache, 
        ILoggerFactory loggerFactory, IStartSession startSession)
    {
        innerStore = new DistributedSessionStore(cache, loggerFactory);
        this.startSession = startSession;
    }

    public ISession Create(string sessionKey, TimeSpan idleTimeout, 
        TimeSpan ioTimeout, Func<bool> tryEstablishSession, 
        bool isNewSessionKey)
    {
        ISession session = innerStore.Create(sessionKey, idleTimeout, ioTimeout,
             tryEstablishSession, isNewSessionKey);
        if (isNewSessionKey)
        {
            startSession.StartSession(session);
        }
        return session;
    }
}

Then register this new class in Startup.cs:

class InitSession : IStartSession
{
    public void StartSession(ISession session)
    {
        session.SetString("Hello", "World");
    }
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddSingleton<IStartSession, InitSession>();
        services.AddSingleton<ISessionStore, DistributedSessionStoreWithStart>();
        services.AddSession();
        ...
    }

Full code is here: https://github.com/SurferJeffAtGoogle/scratch/tree/master/StartSession/MVC

Jeffrey Rennie
  • 3,193
  • 1
  • 18
  • 19
  • 1
    Thanks for your reply. The problem with this and the other approach is that the session middleware, in your case StartSession, is fired for every request. I'm after something that only fires once when session is created. Also, not sure why but it looks like session isn't destroyed when session cookie is deleted? – doogdeb Sep 28 '18 at 13:48
  • I don't think I understand what you describe. – Jeffrey Rennie Sep 28 '18 at 15:40
  • I added a trace statement and breakpoint on StartSession, and it only fired twice: before and after the user agreed to accept cookies. For following requests, it fired no more. Regarding, "session isn't destroyed when the session cookie is deleted." That agrees with my understanding: the server has no way to know when the browser deletes a cookie. – Jeffrey Rennie Sep 28 '18 at 15:52
  • When the session cookie is deleted, the next time you do a post back the session will have been cleared and a new session cookie is created. – doogdeb Oct 01 '18 at 07:14
  • I forgot to update this. It was a Chrome extension which was causing havoc with the session. Jeffrey Rennie's solution worked perfectly. – doogdeb Feb 05 '19 at 15:22
  • I also have the problem of my ISessionStore.Create implementation being fired for every request, in both Chrome and IE11. The sessionKey is different for each ISessionStore.Create call, although this should be the same session. ASP.NET Core 2.2. – Polyfun Nov 15 '19 at 15:10
  • My problem was I had not included the startSession.StartSession(session); call in my ISessionStore.Create implementation. Once I put that in, I no longer get isNewSessionKey == true for each request. – Polyfun Nov 15 '19 at 15:39
  • I figured out the problem: you have to call ISession.SetString during the session creation, otherwise you get a new session for each request. I just added a call to ISession.SetString in my ISessionStore.Create implementation. You don't actually need the IStartSession/InitSession in @Jeffrey Rennie's example. – Polyfun Nov 15 '19 at 15:56
2

I use it in a live project. It works correctly. if you want to keep it when the application stops. You should use DistributedCache. For example, I'm using DistributedRedisCache.

Add to startup this code;

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromMinutes(60);
            options.Cookie.HttpOnly = true;
        });
        // for redis distributed cache

        //services.AddDistributedRedisCache(options =>
        //    {
        //   options.InstanceName = $"{Configuration["DistributedRedisCacheInstance"]}";
        //   options.Configuration = $"{Configuration["DistributedRedisCacheHost"]}";
        //  });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor acc)
    {
        app.UseSession();
    }

And add new session extension;

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.Text;

namespace SampleApp
{
    public static class SessionExtensions
    {
        public static void SetObjectAsJson<T>(this ISession session, string key, T value)
        {
            session.Set(key, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)));
        }

        public static T GetObjectFromJson<T>(this ISession session, string key)
        {
            session.TryGetValue(key, out byte[] dataByte);
            string data = dataByte != null ? Encoding.UTF8.GetString(dataByte) : null;

            return data == null ? default(T) : JsonConvert.DeserializeObject<T>(data);
        }
    }
}

And use get or set same this;

var sessionItem = httpContext.Session.GetObjectFromJson<string>("sessionItem");
//or
ContextProviderExtension.HttpContextAccessor.HttpContext.Session.SetObjectAsJson("sessionItem", sessionItem);

you need this extension;

using Microsoft.AspNetCore.Http;
using System;
    
namespace SampleApp
{
    public static class ContextProviderExtension
    {
        static IHttpContextAccessor httpContextAccessor = null;
        public static IHttpContextAccessor HttpContextAccessor
        {
            get { return httpContextAccessor; }
            set
            {
                if (httpContextAccessor != null)
                {
                    throw new Exception("");
                }
                httpContextAccessor = value;
            }
        }
    }
}

I suppose it will work.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;


namespace SampleApp
{
    public class SessionMiddleware
    {
        private readonly RequestDelegate _next;

        public SessionMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            var sessionItem = httpContext.Session.GetObjectFromJson<string>("test");
            if (sessionItem == null)
                httpContext.Session.SetObjectAsJson<string>("test", httpContext.Session.Id);//httpContext.Session.Id or set a value
            await _next.Invoke(httpContext);
        }
    }

    public static class SessionMiddlewareExtensions
    {
        public static IApplicationBuilder UseSessionMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<SessionMiddleware>();
        }
    }
}

and add startup.cs Configure method after app.UseSession();

app.UseSessionMiddleware();
CodingBarfield
  • 3,392
  • 2
  • 27
  • 54
nercan
  • 197
  • 3
  • 16
  • Thanks for your response. I know how to setup the session and how to set and get from session but I simply want to set a value in the session when it starts but can't see how to accomplish this. Any ideas is appreciated. – doogdeb Sep 27 '18 at 11:57
  • I edited my answer any incoming request will enter into Invoke in SessionMiddleware – nercan Sep 27 '18 at 12:50
  • Not sure how it works exactly but that did the trick. Thanks. – doogdeb Sep 27 '18 at 13:29
  • I have noticed however that the session is persisting even after the browser has been closed. – doogdeb Sep 27 '18 at 13:37
  • Yes, the session continues even if the page is closed for the duration you set. but you can delete session values by sending a request with javascript in this page. https://code.msdn.microsoft.com/How-to-clear-session-when-660c1e6b – nercan Sep 27 '18 at 13:57
  • "The session continues even if the page is closed". In MVC 5 the session was destroyed once the session cookie was deleted or browser closed. Is this no longer the case? – doogdeb Sep 28 '18 at 13:50
  • @doogdeb - there is no signal from browser to server on "closing the browser" so the server cannot use that to destroy the session. That is why it usually has a 20 minute timeout. – Hans Kesting Sep 28 '18 at 15:04
  • When the session cookie is deleted, the next time you do a post back the session will have been cleared and a new session cookie is created – doogdeb Oct 05 '18 at 09:05
0

Oh just save something to the session on every request. If it's already there - it's an existing session. If not - it's a new one.

You can wrap the above idea into a middleware if you want.

The simplest possible version is this:

app.Use(async (context, next) => //add adhoc "middleware"
{
    if (httpContext.Session.GetInt32("initialized") == null) //new session?
    {
        //uh-oh! new session! do your thing!
    
        httpContext.Session.SetInt32("initialized", 1); //save
    }

    await next(context); //run next code
}
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149