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
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.