10

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;
    }
}
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Ben Foster
  • 34,340
  • 40
  • 176
  • 285

3 Answers3

6

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();
        }
    }
}
Ben Foster
  • 34,340
  • 40
  • 176
  • 285
  • Sounds good. But how is the session set then? You set the property of action to be injected. But which function is injected? – Joel Aug 22 '13 at 18:40
  • 1
    This will depend on how you've configured the dependencies with StructureMap. Typically we'd do something like `For().HttpContextScoped().Use(ctx => ctx.GetInstance().OpenSession())` – Ben Foster Aug 23 '13 at 07:50
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 ?

Tomasz Jaskuλa
  • 15,723
  • 5
  • 46
  • 73
0

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.

Robin Clowers
  • 2,150
  • 18
  • 28