1
votes

I asked before a question here, and I read this question/answers about multi-threading and I know those solutions. But today I get a new problem. When we are using commands (or where we can access the original code to manage and modify it) the async-decorator that suggested in above answers works. But when MVC creates a new thread itself, what can we do? e.g. I have a custom role provider (works with DbContext), and I get this error:

The operation cannot be completed because the DbContext has been disposed.

And here is stack trace:

[InvalidOperationException: The operation cannot be completed because the DbContext has been disposed.]

System.Data.Entity.Internal.InternalContext.CheckContextNotDisposed() +67

System.Data.Entity.Internal.LazyInternalContext.InitializeContext() +34

System.Data.Entity.Internal.Linq.DbQueryProvider.Execute(Expression expression) +22

System.Linq.Queryable.Any(IQueryable1 source, Expression1 predicate) +265

MyProject.MyRoleProvider.IsUserInRole(String username, String roleName) in ...

System.Web.Security.Roles.IsUserInRole(String username, String roleName) +263

MyProject.MyPrincipal.IsInRole(String role) in ...

System.Linq.Enumerable.Any(IEnumerable1 source, Func2 predicate) +146

System.Web.Mvc.AuthorizeAttribute.AuthorizeCore(HttpContextBase httpContext) +200

System.Web.Mvc.AuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) +159

System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) +96

System.Web.Mvc.Async.<>c__DisplayClass25.b__1e(AsyncCallback asyncCallback, Object asyncState) +446

System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130

System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +302

System.Web.Mvc.<>c__DisplayClass1d.b__17(AsyncCallback asyncCallback, Object asyncState) +30

System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130

System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +382

System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130

System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +317

System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +15

System.Web.Mvc.<>c__DisplayClass8.b__2(AsyncCallback asyncCallback, Object asyncState) +71

System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130

System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +249

System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +50

System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16

System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301

System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

As you can see, MyProject.MyRoleProvider.IsUserInRole invoked asynchronously, which I did not start it and its invoking async by MVC itself. So I have not any control on it. My provider ctor is:

public MyRoleProvider() { _context = MyIoCWrapper.GetService(); }

It seems when MyRoleProvider instantiated, HttpContext is not null, and when IsInRole called, HttpContext is null. If I want to start a new life scope, it will be used just once, and if MVC starts a new thread, I will have a new DbContext too. I confused to finding a solution. Have you any one? How can I start a new life scope for all background threads -I start them or MVC starts them?

1

1 Answers

1
votes

Your problem is in fact unrelated to running controllers asynchronously, but is a general problem of controlling the lifetime of objects.

You probably registered your MyRoleProvider in the web.config of the application, or perhaps registered it through code. Eitherway, the MyRoleProvider is a singleton, there is just a single instance of that class during the lifetime of the application.

The MyRoleProvider however depends on a DbContext which has a lifestyle that is shorter (Per Web Request or hybrid in your case), which means you can't cache that DbContext during the lifetime of your MyRoleProver, since that would 'promote' the lifestyle of that DbContext to singleton as well. The _context = MyIoCWrapper.GetService(); line seems to indicate that you are caching that DbContext.

In this case you will have to resolve the DbContext instance on each method call. So instead, your IsUserInRole would look something like this:

public bool IsUserInRole(String username, String roleName)
{
    var context = MyIoCWrapper.GetService();
    return context.Roles
        .Any(r => r.Name == roleName && r.User.Name == username);
}