web-dev-qa-db-ja.com

ASP.NET MVCでアクションフィルターを除外する方法は?

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

明らかな修正(ログインアクションを別のコントローラーに移動する以外)は、[承認]をコントローラーからログインを除くすべてのアクションメソッドに移動することです。特に、メソッドがたくさんある場合や、新しいメソッドに[Authorize]を追加するのを忘れた場合は、それは面白くありません。

Railsは、フィルターを除外する機能でこれを簡単にします。 ASP.NETMVCでは許可されていません。そこで、それを可能にすることにしました。思ったより簡単でした。

    /// <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();
    }

}

例はそこにあります。ご覧のとおり、これは非常に簡単で、うまく機能します。誰にでも役立つといいのですが?

26
Steve Potter

私は概説された解決策を好みます ここ 。それはあなたの解決策ほど一般的な解決策ではありませんが、私はそれがもう少し簡単だと思いました。

私の場合、いくつかのアイテムを除くすべてのアイテムで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用であると記載されていますが、MVC1でも十分に機能しているようです。

編集:コメントに応じてここにいくつかの使用法を示しています。上記の変更を行う前は、[DisableCompression]属性が除外したいメソッドにフラグを立てていないことを除いて、まったく同じように見えました。他にリファクタリングはありません。

[CompressionFilter]
public abstract class BaseController : Controller
{
}

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

    [DisableCompression]
    public ActionResult DontWantThisActionCompressed()
    {
        // code
    }
}
25
Gavin

何年も前に、[AllowAnnonymous]属性がASP.NETMVCに追加されていなかったと思います。今日、コントローラーの上に[Authorize]属性を適用して、すべてのActionメソッドに適用できます。特定のアクションに[AllowAnonymous]属性を追加することで、許可されていないユーザーが必要なアクションでこれをオーバーライドするだけです。

0
Dev