2

In my MVC application, I user SignalR for communication between users. Basically, a client calls a method on the hub, which calls a method on a repository, which then saves the message to the database and the hub notifies the other client of the new message.

I had used the GetOwinContext() method during these calls from the client to get the current instance of UserManager and ApplicationDbContext, by using the GetUserManager<UserManager>() and Get<ApplicationDbcontex>() extension methods, respectively. However, I have noticed that calls from the same connection use the same context, which is, obviously, not a very good thing. I went ahead and changed my repository so it is like this now:

    public XyzRepository()  //constructor
    {
        db = ApplicationDbContext.Create(); //static method that generates a new instance
    }
    private ApplicatonDbContext db { get; set; }     
    private UserManager UserManager
    {
        get
        {
            return new UserManager(new UserStore<ApplicationUser>(db)); //returns a new UserManager using the context that is used by this instance of the repository
        }
    }

Since I reference the ApplicationUser objects using the UserManager (using FindByIdAsync(), etc, depending on the design), it is extremely important to use the context I currently work with for the UserStore of the UserManager's current instance. The repository is created once per request, which seems to apply to each SignalR calls as intended. While I have experienced no problems with this design so far, after reading about the issue (in this article), particularly this line:

"In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object.", I decided to ask the community:

Question: what is the preferred way to use ASP.NET Identity's UserManager class with SignalR, if it is imperative that I use the same instance of DbContext for my repository's methods that the UserManager's UserStore uses?

Riwen
  • 4,734
  • 2
  • 19
  • 31

1 Answers1

3

I think the preferred way is to use an Inversion of Control container and constructor-inject dependencies with some kind of lifetime scope. Here is another question that you might want to look into:

Using Simple Injector with SignalR

It is preferable that your DbContext instance live as long as the current web request. IoC containers have facilities that let you register DbContext instances with per web request lifetimes, but you need to set up the IoC container so that it can manage the construction of the Hub classes to achieve this. Some IoC containers (like SimpleInjector) will also automatically dispose of the DbContext at the end of the web request for you, so you don't need to wrap anything in a using block.

As for the UserManager, XyzRepository, etc, I think those can also have per-web-request lifetime, or even transient lifetimes. Ultimately, I don't see why you wouldn't be able to achieve something like this:

public class MyXyzHub : Hub
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly MessageRepository _messageRepository;

    public MyXyzHub(UserManager<ApplicationUser> userManager,
        MessageRepository messageRepository)
    {
        _userManager = userManager;
        _messageRepository= messageRepository;
    }

    public void sendMessage(string message)
    {
        var user = _userManager.FindByIdAsync(...
        _messageRepository.CreateAndSave(new Message
        {
            Content = message, UserId = user.Id 
        });
        Clients.All.receiveMessage(message, user.Name);
    }
}

If you wire up your IoC container the right way, then every time the Hub is constructed, it should reuse the same ApplicationDbContext instance for the current web request. Also with your current code, it looks like XyzRepository is never disposing of your ApplicationDbContext, which is another problem that an IoC container can help you out with.

Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Thanks, I did it. However, Niject is behaving somewhat unexpectedly. Using `InRequestScope()`, after calling the first method on the hub, it creates new instances of the repository, DbContext and UserManager as intended. Calling further methods, though, doesn't result in new instances; the same context is used, which is, of course, very unfortunate. But since it's an unrelated issue, and your answer was perfect, I'm accepting it. – Riwen Jan 02 '15 at 21:46
  • 1
    @Shinzon as long as all of the other methods are being invoked with the same DbContext instance during the same web request, I disagree that is unfortunate. It is intentional and desired IMO. I believe InRequestScope stores the DbContext in HttpContext.Current.Items, and disposes of it during Application_EndRequest. Is that not the case? With this kind of scope your DbContext will not live too long, but will live long enough to do everything needed for a single web request. – danludwig Jan 03 '15 at 02:56
  • The problem is, Application_EndRequest is not triggered when calling a SignalR method from the client, so DbContext is not disposed. – Riwen Jan 03 '15 at 15:58
  • 1
    @Shinzon not sure about Ninject, but SimpleInjector has other scopes such as LifetimeScope and ExecutionScope that you may be able to configure to manage the DbContext lifetime in SignalR when Application_EndRequest is not invoked. May be worth looking into. – danludwig Jan 03 '15 at 17:06
  • Ninject's NamedScope extension's `InCallScope()` seems to have solved the problem. Thank you for your help! – Riwen Jan 03 '15 at 17:29