Action Filter Dependency Injection in ASP.NET MVC

2019-04-05 06:00发布

问题:

I've been playing with the DI support in ASP.NET MVC RC2.

I have implemented session per request for NHibernate and need to inject ISession into my "Unit of work" action filter.

If I reference the StructureMap container directly (ObjectFactory.GetInstance) or use DependencyResolver to get my session instance, everything works fine:

    ISession Session {
        get { return DependencyResolver.Current.GetService<ISession>(); }
    }

However if I attempt to use my StructureMap filter provider (inherits FilterAttributeFilterProvider) I have problems with committing the NHibernate transaction at the end of the request.

It is as if ISession objects are being shared between requests. I am seeing this frequently as all my images are loaded via an MVC controller so I get 20 or so NHibernate sessions created on a normal page load.

I added the following to my action filter:

    ISession Session {
        get { return DependencyResolver.Current.GetService<ISession>(); }
    }

    public ISession SessionTest { get; set; }

    public override void OnResultExecuted(System.Web.Mvc.ResultExecutedContext filterContext) {

        bool sessionsMatch = (this.Session == this.SessionTest);

SessionTest is injected using the StructureMap Filter provider.

I found that on a page with 20 images, "sessionsMatch" was false for 2-3 of the requests.

My StructureMap configuration for session management is as follows:

        For<ISessionFactory>().Singleton().Use(new NHibernateSessionFactory().GetSessionFactory());
        For<ISession>().HttpContextScoped().Use(ctx => ctx.GetInstance<ISessionFactory>().OpenSession());

In global.asax I call the following at the end of each request:

    public Global() {
        EndRequest += (sender, e) => {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        };
    }

Is this configuration thread safe? Previously I was injecting dependencies into the same filter using a custom IActionInvoker. This worked fine until MVC 3 RC2 when I started experiencing the problem above, which is why I thought I would try using a filter provider instead.

Any help would be appreciated.

I'm using NHibernate 3 RC and the latest version of StructureMap

Update:

Below are my implementations of DependencyResolver and FilterAttributeFilterProvider:

    public class StructureMapDependencyResolver : IDependencyResolver {
    private readonly IContainer container;

    public StructureMapDependencyResolver(IContainer container) {
        this.container = container;
    }

    public object GetService(Type serviceType) {
        var instance = container.TryGetInstance(serviceType);
        if (instance==null && !serviceType.IsAbstract){
            instance = AddTypeAndTryGetInstance(serviceType);
        }
        return instance;
    }

    private object AddTypeAndTryGetInstance(Type serviceType) {
        container.Configure(c=>c.AddType(serviceType,serviceType));
        return container.TryGetInstance(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType) {
        return container.GetAllInstances(serviceType).Cast<object>();
    }
}
public class StructureMapFilterAttributeFilterProvider : FilterAttributeFilterProvider
{
    private readonly IContainer container;

    public StructureMapFilterAttributeFilterProvider(IContainer container) {
        this.container = container;
    }

    protected override IEnumerable<FilterAttribute> GetControllerAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
        return BuildUp(base.GetControllerAttributes(controllerContext, actionDescriptor));
    }

    protected override IEnumerable<FilterAttribute> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
        return BuildUp(base.GetActionAttributes(controllerContext, actionDescriptor));
    }

    private IEnumerable<FilterAttribute> BuildUp(IEnumerable<FilterAttribute> attributes) {
        foreach (var attr in attributes)
            container.BuildUp(attr);
        return attributes;
    }
}

回答1:

Thought I would come back and provide the solution.

As @Thomas pointed out above, Action Filters are now cached in MVC 3. This means that if you inject an object with an intended short life time (e.g. http request), it's going to be cached.

To fix, instead of injecting an ISession we inject a Func<ISession>. Then each time we need access to ISession we invoke the function. This ensures that even if the ActionFilter is cached, the ISession is scoped correctly.

I had to configure StructureMap like so to inject the "lazy" instance (unfortunately it doesn't inject a lazy instance automatically like it does with Ctor injection):

            x.SetAllProperties(p => {
                p.OfType<Func<ISession>>();
            });

My updated ActionFilter is below:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class UnitOfWorkAttribute : ActionFilterAttribute {

    public Func<ISession> SessionFinder { get; set; }

    public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext) {
        var session = SessionFinder();
        session.BeginTransaction();
    }

    public override void OnResultExecuted(System.Web.Mvc.ResultExecutedContext filterContext) {         
        var session = SessionFinder();

        var txn = session.Transaction;

        if (txn == null || !txn.IsActive) return;

        if (filterContext.Exception == null || filterContext.ExceptionHandled)
        {
            session.Transaction.Commit();
        }
        else
        {
            session.Transaction.Rollback();
            session.Clear();
        }
    }
}


回答2:

I don't know if it would help but with MVC 3 action filters are now cached instead of being instantiated new at the beginning of every request. So if you're injecting dependencies something in the constructor it won't work well. Could you post your implementation of FilterAttributeFilterProvider ?



回答3:

Did you implement your own IDependencyResolver that uses StructureMap? It seems like you must not have, because you set the session to be HttpContext scoped and yet you are seeing separate sessions during the same request.