具有异步 Ajax 请求的同步 ActionFilter

Synchronous ActionFilter with Async Ajax Request

当我尝试在页面加载时向具有相同 IActionFilter 属性的 2 个 MVC 操作发出 2 个异步 ajax 请求时遇到并发问题。

SimpleInjector注册如下

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());
}

下面是同步 ActionFilter 的 2 个动作

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


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

当ActionFilter 从数据库中请求一些信息时出现问题。我收到以下错误。

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

这只会在每 4 次页面刷新时发生一次。

下面是我的 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);

            ...
        }
}

我可以通过使我的 ajax 请求中的 1 个同步来解决此问题,但这不是真正的解决方案。我也可以通过 1 个请求 return 所有数据,但同样,这不是正确的解决方案。

谢谢

你可以通过简单的扩展方法解决这个问题:

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>();
    }
}

您可以按如下方式使用它:

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

不是将 AuthoriseActionFilter 添加到全局过滤器列表(单例列表),而是添加了 ActionFilterProxy<AuthoriseActionFilter> 的实例。这个代理实例可以安全地用作单例,因为它会在每个请求上回调 container 并为每个请求解析一个新的 AuthoriseActionFilter