1. Qiita
  2. 投稿
  3. ASP.NET

ASP.NET MVCのフィルターを触ってみた

  • 4
    いいね
  • 0
    コメント

この記事はASP.NET Advent Calendar 2016の15日目の記事です。

ASP.NET MVCのFilterについて簡単にまとめてみます。

Filterの種類

承認フィルター

IAuthorizationFilterを実装
セキュリティ的にアクションを実行できるかの判断処理。
他のフィルターより前に実行される。
OnAuthorization・・・承認メソッド
ValidateAntiForgeryTokenAttributeが有名か。

アクションフィルター

IActionFilterを実装
アクションの前後に行いたい処理。
OnActionExecuting・・・アクション前に呼び出されるメソッド
OnActionExecuted・・・アクション後に呼び出されるメソッド

結果フィルター

IResultFilterを実装
ActionResultオブジェクトの実行前後に行いたい処理。
OnResultExecuting・・・ActionResultオブジェクト実行前に呼び出されるメソッド
OnResultExecuted・・・ActionResultオブジェクト実行後に呼び出されるメソッド

例外フィルター

IExceptionFilterを実装
MVC内でスローされた未処理の例外がある場合に呼び出される処理。
OnException・・・例外発生時に呼び出されるメソッド

MyFilterを作ってみた

ここからは独自実装の話になります。

通常、MVCを使ってUA判定でビューを切り替える場合はDisplayModesが使われると思いますが、
UA判定条件をカスタマイズしたかった為、Filterを使って実装してみました。

デバイス種別を持った薄いControllerラッパー
public class MyControllerBase : Controller
{
    // デバイス種別
    public enum DeviceType : byte
    {
        // PC
        PC = 0,
        // スマートフォン
        SmartPhone = 1,
    }
    public DeviceType TargetDevice { get; set; }
}
デバイス判定用ActionFilter属性クラス
public class DeviceJudgmentAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // MyControllerBaseを継承しているControllerクラスであることが前提
        MvcControllerBase ctr = filterContext.Controller as MyControllerBase ;
        if (ctr != null)
        {
            // Requestを渡してデバイス種別を受け取る
            ctr.TargetDeviceType = GetDeviceType(filterContext.HttpContext.Request);
        }
        base.OnActionExecuting(filterContext);
    }

    // デバイス判定処理
    private DeviceType GetDeviceType(HttpRequestBase request)
    {
        // 判定Logicは省略
    }
}

あとは、このフィルターをMyControllerBaseを継承したControllerの好きなところに
属性として指定してあげれば完了です。

OnActionExecutingでアクション実行前にデバイス種別が取得出来ているので、
あとはその後のロジックで有効に活用してあげましょう。

アクションフィルターと承認フィルター

上記のアクションフィルターを実装してUA毎のページ切り分けを行っていたのですが、
実装後しばらくしてから問題があることに気付きました。

問題というのは 「例外フィルターで表示するエラーページをデバイス毎に切り分け」 です。

通常であれば、アクション実行前にフィルターが動いてUA判定を行ってくれるのでいいのですが、
アクション実行前に動いてしまうものがあります。

そうです。 「承認フィルター」 です。

承認処理は全てのフィルターの最初に実行され、承認が通らなかった場合は例外をスローします。

スローされた例外を基本的に例外フィルターで拾ってエラーページを表示していたのですが、
アクションフィルター実行前なのでUA判定が行われておらず、期待したページが表示されなかったのです。

ActionInvokerに無理矢理組み込む

ってことで、各フィルターを呼び出しているActionInvokerを無理矢理拡張しました。

デバイス判定処理を追加したActionInvokerクラス
public class DeviceJudgmentActionInvoker : ControllerActionInvoker
{
    // アクションを呼び出す処理をオーバーライドし、デバイス判定処理を先に行う
    public override bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        // MyControllerBaseを継承しているControllerクラスであることが前提
        MvcControllerBase ctr = filterContext.Controller as MyControllerBase ;
        if (ctr != null)
        {
            // Requestを渡してデバイス種別を受け取る
            ctr.TargetDeviceType = GetDeviceType(filterContext.HttpContext.Request);
        }
        return base.InvokeAction(controllerContext, actionName);
    }

    // デバイス判定処理
    private DeviceType GetDeviceType(HttpRequestBase request)
    {
        // 判定Logicは省略
    }
}
ControllerでActionInvokerの読み込み
public class MyController : MyControllerBase
{
    public MyController()
    {
        // コンストラクタ内でControllerの持つActionInvokerプロパティに、上記ActionInvokerインスタンスを設定
        ActionInvoker = new SetTargetDeviceActionInvoker();
    }
}

この実装で、承認フィルター前にデバイス判定を行うことが出来ました。

ActionInvoker拡張の注意点

上記の方法はフィルター実装前での処理になりますので、
これらの処理の中で例外が発生した場合は例外フィルターで拾えません。