2011-01-13 80 views
23

私はASP.NET MVCでいくつかのケースに遭遇しました。ここでは、1つまたは2つのアクションを除くすべてのアクションにアクションフィルタを適用したいと考えていました。たとえば、AccountControllerがあるとします。その中のすべてのアクションは、ユーザーがログインする必要があるため、コントローラー・レベルで[Authorize]を追加します。しかし、AccountControllerにログインページを含めるとします。問題は、ログインページに送信されたユーザーが承認されていないため、無限ループが発生することです。ASP.NET MVCでアクションフィルタを除外する方法は?

[ログイン]アクションを別のコントローラーに移動する以外にも、[承認]をコントローラーから[ログイン]を除くすべてのアクションメソッドに移動することができます。それは楽しいことではありません。特に、メソッドがたくさんある場合や、新しいメソッドに[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(); 
    } 

} 

例があります。ご覧のとおり、これはかなり簡単で、うまくいきました。それは誰にとっても便利だと思いますか?

+0

'List .RemoveAll'が存在します:http://msdn.microsoft.com/en-us/library/wdka673a.aspx –

+0

ええ、私はList.RemoveAllについて知っています。問題は、System.Web.Mvc.FilterInfoが、List <>であるにもかかわらずList ではなく、IList <>でコレクションを公開することです。リストにキャストしてRemoveAllを使用することができましたが、APIを尊重するのが最善であると感じました。私の小さなヘルパーメソッドはちょっと醜いです、はい。私は通常、コードをもっときれいにする拡張メソッドとしてヘルパーライブラリに組み込まれています。しかし、これはコピー貼り付けでコンパイルしたかったのです。どう思いますか? –

+0

既存のフィルタを除外する別の方法は、IFilterProviderを実装することです。完全なサンプルはこちらhttp://blogs.microsoft.co.il/blogs/oric/archive/2011/10/28/exclude-a-filter.aspx –

答えて

23

私は、hereのアウトラインを推奨します。それは一般的な解決策ではありませんが、少しわかりやすくなりました。

私のケースでは、いくつかの項目を除いてすべてで圧縮フィルタを有効にする方法を探していました。だから私はこのような空の属性を作成しました:

[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

無効にする属性の種類ごとに、新しい "元の属性を変更するだけでなく、その属性のすべての大文字と小文字を置換するようにしてください。私のソリューションに比べて面倒な作業が多いようですが、余分なコードは必要ありません。 DRYを信じている開発者として、あなたのソリューションをより良いものに見える方法は誰にも分かりません。私が見る唯一の利点は、それがもっと明白だということです。しかし何? –

+0

私と他の開発者が理解しやすいので、私は明示的に好きです。そして、現実的には、1つのWebアプリケーションで使用するアクションフィルタの数は、いくつかのアクションを除いてすべてに適用する必要がありますが、確かに非常に低いです。そして余分な4行のコードは、私にとって面倒ではないようです。あなたが他のすべての面倒を手に入れているかどうかは分かりません。 – Gavin

+0

したがって、[Authorize]属性を無効にするには、[Authorize]を[DisableableAuthorize]のようにサブクラス化し、[DisableAuthorize]という新しい名前を作成する必要があります。次に、あなたのアプリで[許可]のすべてのケースを[無効な許可]に置き換えて、誰もが[無効な認証]を使用することを忘れないようにする必要があります。メンテナンスの悪夢や避けることができる2つの新しいクラスのように聞こえる。あなたが言ったように、属性を無効にする必要がある回数はほとんどありません。それで、なぜそのようなトラブルを経験するのですか?一度しか使用されない場合、[ExcludeFilter]属性はすばやく簡単です。 –

0

[AllowAnnonymous]という属性がASP.NET MVCに追加されていないことが前提です。今日私はコントローラーの上に[Authorize]アトリビュートをすべてのアクションメソッドに適用することができます。これを単にアクションでオーバーライドするだけです。承認されていないユーザーには特定のアクションに[AllowAnonymous]アトリビュートを追加する必要があります。

関連する問題