Using Session or HttpContext cookies

This topic is related to the OWIN version of DotVVM only. It is relevant for you only if the application needs to interact with ASP.NET Session, or with cookies using System.Web.HttpContext.

We recommend to avoid using session completely as it causes a wide variety of problems, especially when the user has multiple tabs or browser windows open.

OWIN vs ASP.NET Cookies

OWIN offers its own extensible way of working with cookies. By default, the ChunkingCookieManager class is used. When the application interact with cookies using through System.Web.HttpContext (the classic ASP.NET way), there is a conflict and the changes made by the ChunkingCookieManager will be lost.

DotVVM needs to store CSRF token in a cookie to provide a secure way of executing postbacks. When the browser makes the first request to a DotVVM web application, it stores the CSRF token in the cookie. If ASP.NET session is used at that request, or HttpContext.Current.Response.Cookies collection is changed, the changes to the cookie made by DotVVM are overwritten by the HttpContext and the CSRF token will be lost. When a postback occurs, DotVVM will throw the following exception:

System.Security.SecurityException: SessionID cookie is missing, so can't verify CSRF token

The preferred solution to this problem would be using a different way to store session-related data, or replace the default ChunkingCookieManager class by another implementation which will use HttpContext to interact with cookies.

Using Session in DotVVM applications

To be able to use session in OWIN, the following code should be placed in Startup.cs before any middleware is registered. Otherwise, HttpContext.Current.Session would be null.

app.Use((context, next) =>
{
    var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
    httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
    return next();
});
app.UseStageMarker(PipelineStage.MapHandler);

Then, you need to replace the default cookie manager with the following implementation:

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

The SystemWebCookieManager can be registered in DotvvmStartup.cs using the ConfigureServices method:

public void ConfigureServices(IDotvvmServiceCollection services)
{
    ...
    services.Services.AddSingleton<ICookieManager, SystemWebCookieManager>();
}

On this page