Пользовательский фильтр действий Asp.Net MVC 5 со StructureMap

Я столкнулся с проблемой в пользовательском acitonfilte asp.net mvc с использованием карты структуры в моем классе «LogAttribute».

мой код класса LogAttribute

    public class LogAttribute : ActionFilterAttribute
{
    public ApplicationDbContext Context { get; set; }
    public string Description { get; set; }
    public LogAttribute(string description)
    {
        Description = description;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var userId = filterContext.HttpContext.User.Identity.GetUserId();
        var user = Context.Users.Find(userId); **i am getting error here the Context is coming null here** 
        Context.Logs.Add(new Log(user, filterContext.ActionDescriptor.ActionName,
                                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                                Description
                                )
                        );
        Context.SaveChanges();
    }
}

я создаю другой класс, из которого я передаю значение свойству зависимости установщика

    public class StructureMapFilterProvider : FilterAttributeFilterProvider 
{
    private readonly Func<IContainer> _container;
    public StructureMapFilterProvider(Func<IContainer> container)
    {
        _container = container;
    }

    public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var filters = base.GetFilters(controllerContext, actionDescriptor);
        var container = _container();
        foreach (var filter in filters)
        {
            container.BuildUp(filter.Instance);
            yield return filter;
        }
    }
}

мой код класса преобразователя зависимостей

public class StructureMapDependencyResolver : IDependencyResolver
{
    private readonly Func<IContainer> _containerFactory;
    public StructureMapDependencyResolver(Func<IContainer> containerFactory)
    {
        _containerFactory = containerFactory;
    }
    public object GetService(Type serviceType)
    {
        if (serviceType == null)
        {
            return null;
        }
        var container = _containerFactory();

        return serviceType.IsAbstract || serviceType.IsInterface
            ? container.TryGetInstance(serviceType)
            : container.GetInstance(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return _containerFactory().GetAllInstances(serviceType).Cast<object>();
    }
}

и мой код global.ascx

public class MvcApplication : System.Web.HttpApplication
{

    public IContainer Container
    {
        get
        {
            return (IContainer)HttpContext.Current.Items["_Container"];
        }
        set
        {
            HttpContext.Current.Items["_Container"] = value;
        }
    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        DependencyResolver.SetResolver(new StructureMapDependencyResolver(() => Container ?? ObjectFactory.Container));

        ObjectFactory.Configure(cfg =>
           {
               cfg.Scan(Scan =>
               {
                   Scan.TheCallingAssembly();
                   Scan.WithDefaultConventions();
                   Scan.With(new ControllerConfiguration());
               });
               cfg.For<IFilterProvider>().Use(new StructureMapFilterProvider(() => Container ?? ObjectFactory.Container));
               cfg.For<IUserStore<ApplicationUser>>()
                  .Use<UserStore<ApplicationUser>>();
               cfg.For<DbContext>()
                  .Use(() => new ApplicationDbContext());
               cfg.SetAllProperties(x =>
                x.Matching(p =>
                     p.DeclaringType.CanBeCastTo(typeof(ActionFilterAttribute)) &&
                     p.DeclaringType.Namespace.StartsWith("TestingSturctureMap") &&
                     p.PropertyType.IsPrimitive &&
                     p.PropertyType != typeof(string)));

           });
    }

    public void Application_BeginRequest()
    {
        Container = ObjectFactory.Container.GetNestedContainer();
    }

    public void Application_EndRequest()
    {
        Container.Dispose();
        Container = null;
    }
}

person Developerzzz    schedule 30.04.2014    source источник


Ответы (3)


Я установил пакеты NuGet structuremap и StructureMap.MVC5.
Это добавило папку DependencyResolution и папку StructuremapMvc.cs в папку App_Start.
Затем я создал папку Filters и добавил к ней атрибут, фильтр действий и классы поставщиков фильтров:

Схема решения

Мой класс атрибутов — LogActionsAttribute — самый простой. Это просто атрибут без каких-либо ссылок на какие-либо действия:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class LogActionsAttribute : System.Attribute
{
}

Использование на Controller:

[LogActions]
public class HomeController : System.Web.Mvc.Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Теперь мне нужно было добавить фильтр действий, который бы искал этот атрибут и что-то делал. В моем случае логировать входящие параметры и результат выполнения. ILog — это пользовательский интерфейс с теми двумя методами, которые используются этим фильтром.

ControllerLoggerFilter класс:

public class ControllerLoggerFilter : IActionFilter
{
    private readonly ILog _log;
    private string _request;

    public ControllerLoggerFilter(ILog log)
    {
        _log = log;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (ApplyBehavior(filterContext))
        {
            ActionExecuting(filterContext);
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (ApplyBehavior(filterContext))
        {
            ActionExecuted(filterContext);
        }
    }

    private void ActionExecuting(ControllerContext c)
    {
        if (c == null || c.HttpContext == null)
            return;

        _log.LogInput(c.HttpContext.Request);
        _request = c.HttpContext.Request;
    }

    private void ActionExecuted(ControllerContext c)
    {
        if (c.HttpContext.Response == null)
            return;

        _log.LogOutput(_request, c.HttpContext.Response);
    }

    private static bool ApplyBehavior(ActionExecutingContext filterContext)
    {
        return
            filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttributes(typeof (LogActionsAttribute),
                false).Any();
    }

    private static bool ApplyBehavior(ActionExecutedContext filterContext)
    {
        return
            filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttributes(typeof (LogActionsAttribute),
                false).Any();
    }
}

Теперь мне пришлось сообщить MVC, что этот фильтр должен быть в конвейере. Поскольку он построен контейнером структуры, мне понадобился собственный IFilterProvider:

public class MvcInjectableFilterProvider : IFilterProvider
{
    private readonly IEnumerable<Filter> _list;

    public MvcInjectableFilterProvider(IContainer container)
    {
        _list = GetContainerFilters(container);
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        return _list;
    }

    private static IEnumerable<Filter> GetContainerFilters(IContainer container)
    {
        return
            container.GetAllInstances<IActionFilter>()
                .Select(instance => new Filter(instance, FilterScope.Action, null));
    }
}

Последнее, что нужно сделать: зарегистрировать мои классы в моем IoC.cs.
Это единственное место, которое мне пришлось изменить в файлах, созданных при установке пакетов NuGet.

public static class IoC {
    public static IContainer Initialize() {
        return new Container(c =>
        {
            c.AddRegistry<DefaultRegistry>();
            // added these lines:
            c.For<ILog>().Use<Log>();
            c.For<IActionFilter>().Use<ControllerLoggerFilter>();
            c.For<IFilterProvider>().Use<MvcInjectableFilterProvider>();
        });
    }
}

Источники:
Mark Seemann (и множество других источников). )
Хавьер Г. Лосано
Эрик .sowell
К. Скотт Аллен

person user270576    schedule 10.09.2015

Дорогие друзья, я решаю свой вопрос, просто [SetterProperty] отсутствует в свойстве setter в моем классе LogAttribute.

    public class LogAttribute : ActionFilterAttribute
{
    [SetterProperty]
    public ApplicationDbContext Context { get; set; }
    public string Description { get; set; }
    public LogAttribute(string description)
    {
        Description = description;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var userId = filterContext.HttpContext.User.Identity.GetUserId();
        var user = Context.Users.Find(userId);
        Context.Logs.Add(new Log(user, filterContext.ActionDescriptor.ActionName,
                                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                                Description
                                )
                        );
        Context.SaveChanges();
    }
}

теперь работает :)

person Developerzzz    schedule 30.04.2014

Очень плохая идея иметь свойства setter в ActionFilters! Один и тот же экземпляр actionFilter может использоваться несколькими запросами. В результате разные запросы (потоки) получат одну и ту же ссылку на ApplicationDbContext.

Атрибуты ActionFilterAttributes повторно используются в потоках? Как это работает?

person user3641100    schedule 15.05.2014