我在ASP.NET MVC中遇到过几个案例,我想在除一两个动作之外的每个动作上应用动作过滤器。例如,假设您有一个AccountController。其中的每个操作都需要用户登录,因此您在控制器级别添加[授权]。但是说你想在AccountController中包含登录页面。问题是,发送到登录页面的用户未经授权,因此这将导致无限循环。
明显的修复(除了将Login操作移动到另一个控制器之外)是将[Authorize]从控制器移动到除Login之外的所有操作方法。嗯,这不好玩,特别是当你有很多方法或忘记将[授权]添加到新方法时。
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();
}
}
这个例子就在那里。正如您所看到的,这非常简单,并且工作得很好。我希望它对任何人都有用吗?
答案 0 :(得分:25)
我更喜欢概述here的解决方案。虽然它不像你的解决方案那样通用,但我发现它更直接。
在我的情况下,我一直在寻找一种方法来在除了几个项目之外的所有项目上启用CompressionFilter。所以我创建了一个像这样的空属性:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class DisableCompression : Attribute { }
然后在main属性中,检查是否存在属性:
[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
}
}
答案 1 :(得分:0)
我多年前就认为[AllowAnnonymous]
属性没有被添加到ASP.NET MVC中。今天我可以在我的控制器上应用[Authorize]
属性应用于所有Action方法,我只是简单地在操作中覆盖它,我需要通过向特定操作添加[AllowAnonymous]
属性来要求未经授权的用户。