この記事は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を使って実装してみました。
public class MyControllerBase : Controller
{
// デバイス種別
public enum DeviceType : byte
{
// PC
PC = 0,
// スマートフォン
SmartPhone = 1,
}
public DeviceType TargetDevice { get; set; }
}
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を無理矢理拡張しました。
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は省略
}
}
public class MyController : MyControllerBase
{
public MyController()
{
// コンストラクタ内でControllerの持つActionInvokerプロパティに、上記ActionInvokerインスタンスを設定
ActionInvoker = new SetTargetDeviceActionInvoker();
}
}
この実装で、承認フィルター前にデバイス判定を行うことが出来ました。
ActionInvoker拡張の注意点
上記の方法はフィルター実装前での処理になりますので、
これらの処理の中で例外が発生した場合は例外フィルターで拾えません。