Search code examples
c#asp.netsignalrasp.net-identityowin

Consistent access to IOwinContext in Web Forms, MVC, Signalr


I'm developing a web application and I want to be able to access currently logged in user through my IUserContext interface. It looks like so:

public interface IUserContext
{
    int UserId { get; }
    string UserName { get; }
}

This interface can be consumed in my command handlers and query handlers.

Web application uses ASP.NET Identity library version 2.2.1. So, I want to implement the interface using IOwinContext. I have three layers where I need access to owin context:

  • ASP.NET Web Forms pages
  • ASP.NET MVC 5 actions
  • Signalr hub

For web forms pages and MVC actions, I can write custom middleware to populate IUserContext properties (using code from this doc):

public interface IOwinContextAccessor
{
    IOwinContext CurrentContext { get; }
}

public class CallContextOwinContextAccessor : IOwinContextAccessor
{
    public static AsyncLocal<IOwinContext> OwinContext = new AsyncLocal<IOwinContext>();
    public IOwinContext CurrentContext => OwinContext.Value;
}

public class OwinUserContext : IUserContext
{
    public int UserId => CallContextOwinContextAccessor.OwinContext.Value.Authentication.User.Identity.GetUserId<int>();
    public string UserName => CallContextOwinContextAccessor.OwinContext.Value.Authentication.User.Identity.Name;
}

    // inside my Startup.cs Configuration method
    app.Use(async (context, next) => {
        CallContextOwinContextAccessor.OwinContext.Value = context;
        await next();
    });

For signalr hub I can create HubPipelineModule which will populate CallContextOwinContextAccessor.OwinContext:

public class OwinContextPipelineModule : HubPipelineModule
{
    protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
    {
        var owinContext = context.Hub.Context.Request.GetHttpContext().GetOwinContext();
        CallContextOwinContextAccessor.OwinContext.Value = owinContext;
        return base.OnBeforeIncoming(context);
    }
}

Questions:

  1. Is this the correct way of accessing logged in user in ASP.NET Identity?
  2. Is it safe to reuse CallContextOwinContextAccessor inside middleware and hub pipeline module? I've read documentation about AsyncLocal type and it seems like variable's value persists across methods calls down the call stack. I worry about possible race conditions when one user calls MVC action and at the same time another user calls signalr hub method or vice versa.

Solution

  • Solution:

    As it turned out, it's much easier to use command object and initialize current user property using appropriate mvc/signalr User properties.

    E.g.:

    public class MyCommand
    {
        public string Payload { get; set; }
        public IPrincipal CurrentUser { get; set; } // or int UserId, or string UserName
    }
    

    and then initialize command object:

    // mvc action
    new MyCommand
    {
        Payload = "foo"
        CurrentUser = User // User is a Controller.User property
    };
    
    // signalr hub
    new MyCommand
    {
        Payload = "bar"
        CurrentUser = Context.User
    };
    

    This way, I don't need IUserContext at all. Thanks @Nkosi for figuring that out.