Вопрос по c#, dependency-injection – IFilterProvider и разделение интересов

12

У меня есть ситуация, когда мне нужно вставить некоторые зависимости в фильтр действий, а именно, мой пользовательский поставщик авторизации в мой атрибут пользовательской авторизации. Я наткнулся на множество людей и постов, которые говорили, что мы должны отделять «метаданные атрибута»; от "поведения". Это имеет смысл, и существует также тот факт, что атрибуты фильтра не создаются через «DependencyResolver». поэтому сложно вводить зависимости.

Поэтому я провел небольшой рефакторинг своего кода и хотел узнать, правильно ли я это сделал (я использую Castle Windsor в качестве инфраструктуры DI).

Сначала я удалил свой атрибут, чтобы он содержал только необработанные данные, которые мне нужны

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
    public string Code { get; set; }
}

Я создал собственный фильтр авторизации, который будет содержать логику определения, имеет ли текущий пользователь надлежащую авторизацию

public class MyAuthorizationFilter : IAuthorizationFilter
{
    private IAuthorizationProvider _authorizationProvider;
    private string _code;

    public MyAuthorizationFilter(IAuthorizationProvider authorizationProvider, string code)
    {
        Contract.Requires(authorizationProvider != null);
        Contract.Requires(!string.IsNullOrWhiteSpace(code));

        _authorizationProvider = authorizationProvider;
        _code = code;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            BaseController controller = filterContext.Controller as BaseController;
            if (controller != null)
            {
                if (!IsAuthorized(controller.CurrentUser, controller.GetCurrentSecurityContext()))
                {
                    // forbidden
                    filterContext.RequestContext.HttpContext.Response.StatusCode = 403;
                    if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
                    {
                        filterContext.Result = new RedirectToRouteResult("default", new RouteValueDictionary(new
                        {
                            action = "http403",
                            controller = "error"
                        }), false);
                    }
                    else
                    {
                        filterContext.Result = controller.InvokeHttp404(filterContext.HttpContext);
                    }
                }
            }
            else
            {

            }
        }
        else
        {
            filterContext.Result = new RedirectResult(FormsAuthentication.LoginUrl);
        }
    }

    private bool IsAuthorized(MyUser user, BaseSecurityContext securityContext)
    {
        bool has = false;
        if (_authorizationProvider != null && !string.IsNullOrWhiteSpace(_code))
        {
            if (user != null)
            {
                if (securityContext != null)
                {
                    has = _authorizationProvider.HasPermission(user, _code, securityContext);
                }
            }
        }
        else
        {
            has = true;
        }
        return has;
    }
}

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

public class MyAuthorizationFilterProvider : IFilterProvider
{
    private IWindsorContainer _container;

    public MyAuthorizationFilterProvider(IWindsorContainer container)
    {
        Contract.Requires(container != null);
        _container = container;
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        Type controllerType = controllerContext.Controller.GetType();
        var authorizationProvider = _container.Resolve<IAuthorizationProvider>();
        foreach (MyAuthorizeAttribute attribute in controllerType.GetCustomAttributes(typeof(MyAuthorizeAttribute), false))
        {
            yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Controller, 0);
        }
        foreach (MyAuthorizeAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(MyAuthorizeAttribute), false))
        {
            yield return new Filter(new MyAuthorizationFilter(authorizationProvider, attribute.Code), FilterScope.Action, 0);
        }
    }
}

Последний шаг - регистрация поставщика фильтров в global.asax.

FilterProviders.Providers.Add(new MyAuthorizationFilterProvider(_container));

Поэтому мне интересно, во-первых, правильно ли я понял идею, а во-вторых, что можно улучшить.

Привет, Франсуа, я нашел довольно похожее решение для той же проблемы, что и ты. В настоящее время я задаю себе те же вопросы, что и вы. Вы в конечном итоге использовали это решение? Есть какие-то проблемы с течением времени? Есть ли у вас какие-либо рекомендации? Спасибо Jean-Francois

Ваш Ответ

2   ответа
3

я думаю, ты понял идею правильно. Мне нравится, что вы разделяете проблемы между атрибутом и реализацией фильтра, и мне нравится, что вы используете конструктор DI, а не свойство DI.

Ваш подход работает хорошо, если у вас есть только один тип фильтра. Я думаю, что самой большой потенциальной областью для улучшения, если бы у вас было более одного типа фильтров, было бы то, как реализован поставщик фильтров. В настоящее время поставщик фильтра тесно связан с атрибутом и экземплярами фильтра, которые он предоставляет.

Если вы хотите объединить атрибут с фильтром и использовать свойство DI, существует простой способ получить более разобщенного поставщика фильтров. Вот два примера такого подхода:http: //www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in-asp-net-mvc- http: //lozanotek.com/blog/archive/2010/10/12/dependency_injection_for_filters_in_mvc3.asp

С помощью текущего подхода необходимо решить две проблемы: 1. Внедрить некоторые, но не все, параметры конструктора фильтра через DI. 2. Преобразование из атрибута в (внедренный зависимостью) экземпляр фильтра.

В настоящее время вы делаете оба вручную, что, конечно, хорошо, когда есть только один фильтр / атрибут. Если бы их было больше, вам, вероятно, понадобился бы более общий подход к обеим частям.

Для задания № 1 вы можете использовать что-то вроде перегрузки _container.Resolve, которая позволяет передавать аргументы. Это решение довольно специфично для контейнера и, возможно, немного сложнее.

Другое решение, которое я опишу здесь, отделяет фабричный класс, который принимает только зависимости в своем конструкторе и создает экземпляр фильтра, требующий аргументов как DI, так и не-DI.

Вот как может выглядеть эта фабрика:

public interface IFilterInstanceFactory
{
    object Create(Attribute attribute);
}

Затем вы реализуете фабрику для каждой пары атрибут / фильтр:

public class MyAuthorizationFilterFactory : IFilterInstanceFactory
{
    private readonly IAuthorizationProvider provider;

    public MyAuthorizationFilterFactory(IAuthorizationProvider provider)
    {
        this.provider = provider;
    }

    public object Create(Attribute attribute)
    {
        MyAuthorizeAttribute authorizeAttribute = attribute as MyAuthorizeAttribute;

        if (authorizeAttribute == null)
        {
            return null;
        }

        return new MyAuthorizationFilter(provider, authorizeAttribute.Code);
   }
}

Вы можете решить задачу №2, просто зарегистрировав каждую реализацию IFilterInstanceFactory в CastleWindsor.

Поставщик фильтров теперь можно отделить от любых знаний о конкретных атрибутах и фильтрах:

public class MyFilterProvider : IFilterProvider
{
    private IWindsorContainer _container;

    public MyFilterProvider(IWindsorContainer container)
    {
        Contract.Requires(container != null);
        _container = container;
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        Type controllerType = controllerContext.Controller.GetType();
        var authorizationProvider = _container.Resolve<IAuthorizationProvider>();
        foreach (FilterAttribute attribute in controllerType.GetCustomAttributes(typeof(FilterAttribute), false))
        {
            object instance = Resolve(attribute);
            yield return new Filter(instance, FilterScope.Controller, 0);
        }
        foreach (FilterAttribute attribute in actionDescriptor.GetCustomAttributes(typeof(FilterAttribute), false))
        {
            object instance = Resolve(attribute);
            yield return new Filter(instance, FilterScope.Action, 0);
        }
    }

    private object Resolve(Attribute attribute)
    {
        IFilterInstanceFactory[] factories = _container.ResolveAll<IFilterInstanceFactory>();

        foreach (IFilterInstanceFactory factory in factories)
        {
            object dependencyInjectedInstance = factory.Create(attribute);

            if (dependencyInjectedInstance != null)
            {
                return dependencyInjectedInstance;
            }
        }

        return attribute;
    }
}

Дэвид

Возможно, что-то не хватает, но этот код выше фактически не возвращает экземпляр атрибута (не связанный фильтр).code object instance = Resolve (attribute); yield return new Filter (экземпляр, FilterScope.Action, 0);code Jon Hilton
Не обращай на меня внимания, перечитал и понял, что фабрика занимается созданием фильтров. Jon Hilton
0

вероятно, немного, но один из способов избежать фабрики, предложенной Дэвидом (и сделать это немного более общим), - это ввести еще один атрибут.

[AssociatedFilter(typeof(MyAuthorizationFilter))]

Который можно добавить к исходному атрибуту следующим образом.

[AssociatedFilter(typeof(MyAuthorizationFilter))]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : Attribute
{
    public string Code { get; set; }
}

Атрибут AssociatedFilter выглядит следующим образом.

public class AssociatedFilterAttribute : Attribute
{
    public AssociatedFilterAttribute(Type filterType)
    {
        FilterType = filterType;
    }
    public Type FilterType { get; set; }
}

Затем вы можете получить правильный фильтр, вытянув FilterType из этого атрибута.

private object Resolve(Attribute attribute)
{
    var filterAttributes = attribute.GetType().GetCustomAttributes(typeof(AssociatedFilterAttribute), false);
    var first = (AssociatedFilterAttribute)filterAttributes.FirstOrDefault();
    return new Filter(_container.Resolve(first.FilterType), FilterScope.First, null); 
}

В настоящее время это ограничивается только получением первого атрибута AssociatedFilter, теоретически, я думаю, вы могли бы добавить более одного (один атрибут запускает несколько фильтров), и в этом случае вы пропускаете бит, в котором это захватывает первый результат.

Очевидно, нам также нужно добавить обработку ошибок, например, если нет AssociatedFilterAttribute ...

Похожие вопросы