Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DI/IoC for .net mvc async/await

I'm using Simple Injector as the IoC container for my .Net MVC project. Here is how I register the services.

SimpleInjectorInitializer.cs

 public static void Initialize() {
    var container = new Container();

    container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();  
    //container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle(); // replace last line with this for async/await

    InitializeContainer(container);
    container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    container.Verify();
    DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
}

private static void InitializeContainer(Container container) {
    container.Register<MyDbContext>(Lifestyle.Scoped);
    container.Register(typeof(IUnitOfWork<>), typeof(UnitOfWork<>), Lifestyle.Scoped);
    container.Register(typeof(IRepository<>), typeof(Repository<>), Lifestyle.Scoped);
    container.Register<ICustomerService, CustomerService>(Lifestyle.Scoped);

    //what does this do by the way?
    //using (container.BeginExecutionContextScope()) {
    //}
}

CustomerController

public interface ICustomerService : IService<Customer> {}

public class CustomerService : BaseService<Customer, MyDbContext>, ICustomerService {
    public CustomerService(IUnitOfWork<MyDbContext> unitOfWork) : base(unitOfWork) {}
    // do stuff
}

public class CustomerController : Controller {
    private readonly ICustomerService _service;

    public CustomerController(ICustomerService service) {
        _service = service;
    }

    public ActionResult Index() {
        var foo = _service.GetById(112); // works
        // do stuff
        return View();
    }

    public async Task<int> Foo() { // error out on calling this method
        var foo = await _service.GetByIdAsync(112); 
        return foo.SomeId;
    }
}

My problem is that whenever I used async/await, the ioc failed. Then I looked its documentation, it got a different LifeStyle for Asynchronous methods. So I changed the DefaultScopeLifeStyle to ExecutionContextScopeLifestyle(), it errored out

The ICustomerService is registered as 'Execution Context Scope' lifestyle, but the instance is requested outside the context of a Execution Context Scope.

Do I need to implement a hybrid lifestyle for using asyn/await as well as synchronous methods? Or something is wrong in my design?

Error detail (with WebRequestLifestyle)

The asynchronous action method 'foo' returns a Task, which cannot be executed synchronously.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The asynchronous action method 'foo' returns a Task, which cannot be executed synchronously.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[InvalidOperationException: The asynchronous action method 'foo' returns a Task, which cannot be executed synchronously.] System.Web.Mvc.Async.TaskAsyncActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) +119 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) +27 System.Web.Mvc.<>c__DisplayClass15.b__12() +56 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func1 continuation) +256 System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +22 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList1 filters, ActionDescriptor actionDescriptor, IDictionary2 parameters) +190 System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +522 NotFoundMvc.ActionInvokerWrapper.InvokeActionWith404Catch(ControllerContext controllerContext, String actionName) +32 NotFoundMvc.ActionInvokerWrapper.InvokeAction(ControllerContext controllerContext, String actionName) +16 System.Web.Mvc.<>c__DisplayClass22.<BeginExecuteCore>b__1e() +23 System.Web.Mvc.Async.AsyncResultWrapper.<.cctor>b__0(IAsyncResult asyncResult, Action action) +15 System.Web.Mvc.Async.WrappedAsyncResult2.CallEndDelegate(IAsyncResult asyncResult) +16 System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +49 System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +36 System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller) +12 System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +22 System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +49 System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +26 System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10 System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +21 System.Web.Mvc.Async.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) +29 System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49 System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +28 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9765121 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

Edit I've confirmed it's not a Simple Injector issue, it's really this. I've tried to clean the solution, remove the dlls in the bin folder, still no luck with same error. However, I changed the controller to an ApiController, the asyn worked well.

like image 788
Quentin Avatar asked Oct 30 '22 12:10

Quentin


1 Answers

As far as I can see, this problem is not related to Simple Injector and its scoping; if you wrap an execution context scope around a web request (which is something you can do by hooking into the request_start and request_end events), you will be faced by the same problem.

There are several issues about this on Stackoverflow and the rest of the interwebs, that you should take a look at, such as this q/a.

like image 73
Steven Avatar answered Nov 15 '22 04:11

Steven