Способ исключить фильтры действий в ASP.NET MVC? - PullRequest
25 голосов
/ 13 января 2011

Я столкнулся с несколькими случаями в 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();
    }

}

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

Ответы [ 2 ]

25 голосов
/ 17 мая 2011

Я предпочитаю решение, изложенное здесь .Хотя это не такое универсальное решение, как ваше, я нашел его более простым.

В моем случае я искал способ включить 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
    }
}
0 голосов
/ 20 июня 2015

Я предполагаю, что несколько лет назад атрибут [AllowAnnonymous] не был добавлен в ASP.NET MVC. Сегодня у меня может быть атрибут [Authorize] поверх моего контроллера, применяемый ко всем методам Action, и я просто переопределяю это в действиях, которые требуются неавторизованным пользователям, добавляя атрибуты [AllowAnonymous] к определенным действиям.

...