Инъекция зависимостей фильтра действий в ASP.NET MVC 3 RC2 с StructureMap

Я играл с поддержкой DI в ASP.NET MVC RC2.

Я выполнил сеанс на запрос для NHibernate и вам нужно вставить ISession в мой фильтр действий "Единица работы".

Если я напрямую ссылаюсь на контейнер StructureMap (ObjectFactory.GetInstance) или используйте DependencyResolver для получения экземпляра сеанса, все работает нормально:

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

Однако, если я попытаюсь использовать поставщика фильтра StructureMap (наследует FilterAttributeFilterProvider), у меня возникают проблемы с фиксацией транзакции NHibernate в конце запроса.

Как будто ISession объекты разделяются между запросами. Я часто это вижу, так как все мои изображения загружаются через контроллер MVC, поэтому я получаю 20 или около того сеансов NHibernate, созданных при нормальной загрузке страницы.

Я добавил в свой фильтр действий следующее:

    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 вводится с использованием поставщика StructureMap Filter.

Я обнаружил, что на странице с 20 образами "sessionMatch" был ложным для 2-3 запросов.

Конфигурация My StructureMap для управления сеансом выглядит следующим образом:

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

В global.asax я вызываю следующее в конце каждого запроса:

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

Безопасна ли эта конфигурация? Раньше я вводил зависимости в один и тот же фильтр, используя пользовательский IActionInvoker. Это работало нормально до MVC 3 RC2, когда я начал испытывать проблему выше, поэтому я думал, что попробую использовать поставщик фильтра.

Любая помощь будет оценена.

Я использую NHibernate 3 RC и последнюю версию StructureMap

Update:

Ниже приведены мои реализации DependencyResolver и 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

Думаю, что я вернусь и предоставил решение.

Как отметил @Thomas, Action Filter теперь кэшируется в MVC 3. Это означает, что если вы введете объект с предполагаемым коротким временем жизни (например, http-запрос), он будет кэшироваться.

Чтобы исправить, вместо инъекции ISession вводим a Func<ISession>. Затем каждый раз, когда нам нужен доступ к ISession, мы вызываем функцию. Это гарантирует, что даже если ActionFilter будет кэширован, ISession будет правильно помечен.

Мне пришлось настроить StructureMap так, чтобы вставить "ленивый" экземпляр (к сожалению, он не вводит ленивый экземпляр автоматически, как это делает с инъекцией Ctor):

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

Мой обновленный ActionFilter находится ниже:

[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

Я не знаю, поможет ли это, но с фильтрами действий MVC 3 теперь кэшируются, а не создаются в начале каждого запроса. Поэтому, если вы впрыскиваете зависимости в конструкторе, это не сработает. Не могли бы вы опубликовать свою реализацию FilterAttributeFilterProvider?

Ответ 3

Вы реализовали свой собственный IDependencyResolver, который использует StructureMap? Похоже, что этого не должно быть, потому что вы задали сеанс HttpContext, но все же вы видите отдельные сеансы во время одного и того же запроса.