Приписывает конкретные действия.

лкнулся с несколькими случаями в ASP.NET MVC, где я хотел применить фильтр действий к каждому действию, кроме одного или двух. Например, скажем, у вас есть AccountController. Каждое действие требует входа пользователя, поэтому вы добавляете [Authorize] на уровне контроллера. Но скажем, вы хотите включить страницу входа в AccountController. Проблема в том, что пользователи, отправленные на страницу входа, не авторизованы, поэтому это может привести к бесконечному циклу.

Очевидное решение (кроме перемещения действия «Вход в систему» ​​на другой контроллер) заключается в перемещении [Авторизовать] из контроллера во все методы действия, кроме «Вход в систему». Ну, это не весело, особенно если у вас много методов или вы забыли добавить [Authorize] в новый метод.

Rails делает это проще с возможностью исключать фильтры. ASP.NET MVC не позволяет вам. Поэтому я решил сделать это возможным, и это оказалось проще, чем я думал.

    /// <summary>
/// This will disable any filters of the given type from being applied.  This is useful when, say, all but on action need the Authorize filter.
/// </summary>
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class, AllowMultiple=true)]
public class ExcludeFilterAttribute : ActionFilterAttribute
{

    public ExcludeFilterAttribute(Type toExclude)
    {
        FilterToExclude = toExclude;
    }

    /// <summary>
    /// The type of filter that will be ignored.
    /// </summary>
    public Type FilterToExclude
    {
        get;
        private set;
    }
}

/// <summary>
/// A subclass of ControllerActionInvoker that implements the functionality of IgnoreFilterAttribute.  To use this, just override Controller.CreateActionInvoker() and return an instance of this.
/// </summary>
public class ControllerActionInvokerWithExcludeFilter : ControllerActionInvoker
{
    protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        //base implementation does all the hard work.  we just prune off the filters to ignore
        var filterInfo = base.GetFilters(controllerContext, actionDescriptor);           
        foreach( var toExclude in filterInfo.ActionFilters.OfType<ExcludeFilterAttribute>().Select(f=>f.FilterToExclude).ToArray() )
        {
            RemoveWhere(filterInfo.ActionFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
            RemoveWhere(filterInfo.AuthorizationFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
            RemoveWhere(filterInfo.ExceptionFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
            RemoveWhere(filterInfo.ResultFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
        }
        return filterInfo;
    }


    /// <summary>
    /// Removes all elements from the list that satisfy the condition.  Returns the list that was passed in (minus removed elements) for chaining.  Ripped from one of my helper libraries (where it was a pretty extension method).
    /// </summary>
    private static IList<T> RemoveWhere<T>(IList<T> list, Predicate<T> predicate)
    {

        if (list == null || list.Count == 0)
            return list;
        //note: didn't use foreach because an exception will be thrown when you remove items during enumeration
        for (var i = 0; i < list.Count; i++)
        {
            var item = list[i];
            if (predicate(item))
            {
                list.RemoveAt(i);
                i--;
            }
        }
        return list;
    }
}

/// <summary>
/// An example of using the ExcludeFilterAttribute.  In this case, Action1 and Action3 require authorization but not Action2.  Notice the CreateActionInvoker() override.  That's necessary for the attribute to work and is probably best to put in some base class.
/// </summary>
[Authorize]
public class ExampleController : Controller
{
    protected override IActionInvoker CreateActionInvoker()
    {
        return new ControllerActionInvokerWithExcludeFilter();
    }

    public ActionResult Action1()
    {
        return View();
    }

    [ExcludeFilter(typeof(AuthorizeAttribute))]
    public ActionResult Action2()
    {
        return View();
    }

    public ActionResult Action3()
    {
        return View();
    }

}

Пример прямо здесь. Как видите, это было довольно просто сделать и прекрасно работает. Надеюсь это кому-нибудь пригодится?

 Ian Mercer14 янв. 2011 г., 01:03
List<T>.RemoveAll существует:msdn.microsoft.com/en-us/library/wdka673a.aspx
 Ori Calvo29 окт. 2011 г., 03:06
Еще один способ исключить существующий фильтр - реализовать IFilterProvider. Смотрите полный образец здесь:blogs.microsoft.co.il/blogs/oric/archive/2011/10/28/...
 JotaBe05 дек. 2013 г., 12:55
Если у вас есть ответ на свой вопрос, его не следует включать в сам вопрос, редактируя его. Вместо этого вы должны ответить себе и отметить свой ответ как принятый.
 Steve Potter14 янв. 2011 г., 16:50
Да, я знаю о List.RemoveAll. Проблема заключается в том, что System.Web.Mvc.FilterInfo предоставляет эти коллекции как IList <>, а не List <T>, хотя базовой реализацией является List <>. Я мог бы привести к List <T> и использовать RemoveAll, но я чувствовал, что лучше всего соблюдать API. Мой маленький вспомогательный метод немного уродлив, да. У меня обычно это скрыто в вспомогательной библиотеке как метод расширения, что делает код намного чище. Но для этого я хотел, чтобы он скомпилировался с помощью копирования пасты. Как вы думаете?
 JotaBe05 дек. 2013 г., 13:10
Я не думаю, что это хорошее решение: вам нужно помнить, чтобы переопределить CreateActionInvoker во всех ваших контроллерах, если у вас нет базового контроллера. Правильное решение - реализовать собственный ControllerFactory и установить в нем активатор действий. Вы можете сделать это, как описано здесь:stackoverflow.com/questions/568137/...

Ответы на вопрос(1)

Вот, Хотя это не такое универсальное решение, как ваше, я нашел его более простым.

В моем случае я искал способ включить CompressionFilter для всего, кроме нескольких элементов. Итак, я создал пустой атрибут, как это:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class DisableCompression : Attribute { }

Затем в главном атрибуте проверьте наличие атрибута следующим образом:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class CompressionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        bool disabled = filterContext.ActionDescriptor.IsDefined(typeof(DisableCompression), true) ||
                        filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(DisableCompression), true);
        if (disabled)
            return;

        // action filter logic here...
    }
}

Хотя на странице, на которую я ссылался, упоминается, что это для MVC 3, похоже, она работает достаточно хорошо и в MVC 1.

РЕДАКТИРОВАТЬ: показывает некоторое использование здесь в ответ на комментарии. До того, как я внес изменения выше, он выглядел именно так, за исключением того, что без атрибута [DisableCompression] отмечен метод, который я хотел исключить. Там нет никакого другого рефакторинга.

[CompressionFilter]
public abstract class BaseController : Controller
{
}

public class SomeController : BaseController
{
    public ActionResult WantThisActionCompressed()
    {
        // code
    }

    [DisableCompression]
    public ActionResult DontWantThisActionCompressed()
    {
        // code
    }
}
 Steve Potter17 мая 2011 г., 15:15
Для каждого типа атрибута, который вы хотите отключить, вам нужно создать новый атрибут «отключить», а также изменить исходный атрибут и обязательно заменить все случаи этого атрибута в вашем коде. Похоже, очень громоздкая работа по сравнению с моим решением, которое не требует никакого дополнительного кода. Как разработчик, который верит в СУХОЙ, я не понимаю, как кто-то мог бы увидеть ваше решение как лучшее. Единственное преимущество, которое я вижу, состоит в том, что это более явно. Ну и что?
 Steve Potter19 мая 2011 г., 00:06
Таким образом, чтобы отключить атрибут [Authorize], вам необходимо создать подкласс [Authorize] во что-то вроде [DisableableAuthorize], а затем создать новый с именем [DisableAuthorize]. ТОГДА вам нужно заменить все случаи [Authorize] в вашем приложении на [DisableableAuthorize] и убедиться, что все помнят об использовании [DisableableAuthorize]. Походит на кошмар обслуживания, а также 2 новых класса, которых можно было избежать. И, как вы сказали, количество раз, когда вам нужно отключить атрибуты, мало. Так зачем переживать все эти неприятности? Атрибут [ExcludeFilter] является быстрым и легким, если используется только один раз.
 Gavin17 мая 2011 г., 16:15
Мне нравится явное, потому что мне и другим разработчикам легче понять. И реально, количество фильтров действий, которые используются в одном веб-приложении, которые также должны применяться ко всем, кроме нескольких действий, безусловно, очень мало. И 4 дополнительных строки кода не кажутся мне громоздкими. Не уверен, откуда вы получаете все другие громоздкие, поскольку использование его в значительной степени работает на том же принципе, что и у вас.
 Gavin20 мая 2011 г., 03:41
Почему я должен разделить мой фильтр действий [Authorize] на [DisableableAuthorize]? Я просто добавляю две строки в свой существующий фильтр действий [Авторизация]. Они проверяют наличие атрибута [DisableAuthorize]. Вот и все. 4 строки. Больше ничего не нужно заменять. Или вы говорите о фильтрах действий, которые вы не можете изменить? Если [Authorize] фильтр - это класс .NET, который вы не можете изменить, тогда хорошо, но в моем ответе это явно не так.
 Steve Potter31 мая 2011 г., 17:41
В вашем решении отключение любого типа атрибута включает подкласс этого атрибута, а также сопутствующий атрибут «disable». И вы забудете, что вам придется потенциально переопределять OnActionExecuted, OnResultExecuting и OnResultExecuted, а также OnActionExecuting. Путь больше, чем 4 строки, если вы хотите сделать это правильно. Во всяком случае, я отпущу проблему. Я думаю, что это больше о фундаментальных разногласиях. Я предпочитаю многократно используемый код, в то время как вижу, что вы цените более очевидное решение. Спасибо за отличную дискуссию!

Ваш ответ на вопрос