Search code examples
c#asp.netajaxasynchronoussimple-injector

Synchronous ActionFilter with Async Ajax Request


I am having concurrency issues when trying to make 2 async ajax requests on page load to 2 MVC actions which have the same IActionFilter attribute.

The SimpleInjector registration is as follows

public static Container Initialize(IAppBuilder app)
{
    var container = GetInitializeContainer(app);
    RegisterGlobalFilters(GlobalFilters.Filters, container);
    container.Verify();

    DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

   return container;
}

private static Container GetInitializeContainer(IAppBuilder app)
{
    var container = new Container();
    container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

    container.RegisterSingleton(app);

    RegisterCustomDependacies(container);

    container.Register(() => new ApplicationDbContext(), Lifestyle.Scoped);

    container.RegisterMvcControllers(Assembly.GetExecutingAssembly());

    //At the moment of verification, there is now HttpContext to check. So create a new owin context to check instead and get the authentication manager.
    container.Register(() => AdvancedExtensions.IsVerifying(container) ? new OwinContext(new Dictionary<string, object>()).Authentication
        : HttpContext.Current.GetOwinContext().Authentication, Lifestyle.Scoped);

    return container;
}

public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
{
        //Originally had this but it was causing the same issues
        //filters.Add(new AuthoriseActionFilter(container.GetInstance<ICacheManager>(), 
        //    container.GetInstance<IUserManager>(), 
        //    container.GetInstance<IAuthorisationManager>(), 
        //    container.GetInstance<IClientManager>(), 
        //    container.GetInstance<ICookieManager>(),
        //    container.GetInstance<IAuthenticator>()
        //    ));

    container.Register<IActionFilter, AuthoriseActionFilter>(Lifestyle.Scoped);
    filters.Add(container.GetInstance<IActionFilter>());
    filters.Add(new HandleErrorAttribute());
}

Below are the 2 actions with the synchronous ActionFilter

[AuthoriseAction("Can_Access_Summary")]
public async Task<PartialViewResult> ReturnPots(int? dateID, string date) {}


[AuthoriseAction("Can_Access_Summary")]
public async Task<string> GetImportedDates() {}

The problem occurs when the ActionFilter requests some information from the database. I get the following error.

There is already an open DataReader associated with this Connection which must be closed first.

This only happens once every 4 page refreshes.

Below is a simplified version of my IActionFilter

public void OnActionExecuting(ActionExecutingContext filterContext)
{
    try
    {
        var attribute = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthoriseAction), false).SingleOrDefault() as AuthoriseAction;
        if (attribute != null)
        {
            var userID = _cookieManager.DecryptFormsAuthenticationCookie(filterContext.HttpContext);

            if (userID == -1) { filterContext.Result = new Http401Result(); return; }

            var user = _userManager.LoadByIDSync(userID);

            var isAuthenticated = _authenticator.AuthenticateUserSync(user);

            ...
        }
}

I can fix this by making 1 of my ajax requests synchronous, but that isn't a real solution. I also could return all the data with 1 request but again, its not really the correct solution.

Thanks


Solution

  • You can solve this problem with a simple extension method:

    internal static class SimpleInjectorMvcActionFilterExtensions
    {
        public static void AddFilter<TActionFilter>(
            this GlobalFilterCollection filters, Container container)
             where TActionFilter : class, IActionFilter
        {
           // Register instance in the container to allow this instance to be
           // diagnosed.
           container.Register<TActionFilter>(Lifestyle.Scoped);
    
           // Add a proxy to the global filters that resolves the real instance
           filters.Add(new ActionFilterProxy<TActionFilter>(container));
        }
    
        private sealed class ActionFilterProxy<TActionFilter> : IActionFilter 
            where TActionFilter : class, IActionFilter
        {
           private readonly Container container;
           public ActionFilterProxy(Container container) { this.container = container; }
           public void OnActionExecuting(ActionExecutingContext c)=> Get().OnActionExecuting(c);
           public void OnActionExecuted(ActionExecutedContext c)=> Get().OnActionExecuted(c);
           private TActionFilter Get() => container.GetInstance<TActionFilter>();
        }
    }
    

    You can use this as follows:

    public static void RegisterGlobalFilters(
        GlobalFilterCollection filters, Container container)
    {
        // Call extension method
        filters.AddFilter<AuthoriseActionFilter>(container);
        filters.Add(new HandleErrorAttribute());
    }
    

    Instead of adding the AuthoriseActionFilter to the list of global filters (a list of singletons), an instance of ActionFilterProxy<AuthoriseActionFilter> is added. This proxy instance can safely be used as singleton, because it will call back into the container on each request and resolve a new AuthoriseActionFilter for each request.