みなさんこんにちは!ヒロポンです!!
コントローラのアクションを開くたびに、こんな行が並んでませんか??
if (Session["UserId"] == null) return RedirectToAction("Login");
_logger.Info("Index 開始");
var sw = Stopwatch.StartNew();
ログイン確認、ログ出力、実行時間の計測。
どのアクションにも同じ前処理・後処理をコピペしてる。1個直すたびに全アクションを直して回る、あれです。
これ、ASP.NET MVC の アクションフィルター に寄せると、コピペが1箇所に集約できます。
今日は ASP.NET のアクションフィルターの使い方を、4種類の役割分担と実行順序から積み上げて解説します。「認証→アクション→結果」の順序を誤解すると例外処理が効かない、という詰まりどころまで。
アクションフィルターは何を解決するのか
「ログイン確認」「例外ログ」「実行時間の計測」——これらは、どの機能にも共通で必要だけど、その機能の本質ではない 処理です。
こういうのを 横断的関心事(cross-cutting concern) と呼びます。
本質じゃないのに、アクションごとに毎回書く。しかも書き忘れるとログイン確認が抜けてセキュリティホールになる。
アクションフィルターは、この横断的な処理を アクションの前後に自動で挟み込む 仕組みです。こんな感じで、本質じゃない処理を外に追い出せる。コントローラ本体は業務ロジックだけに集中できます。
ここまでのポイント: フィルター=「どのアクションにも共通する前後処理」を1箇所にまとめる仕組み。
フィルターが無い時代の書き方
フィルターを使わないと、こうなります。
public ActionResult Index()
{
if (Session["UserId"] == null) return RedirectToAction("Login");
var sw = Stopwatch.StartNew();
try
{
// ここが本当にやりたい処理
var data = _service.GetData();
return View(data);
}
catch (Exception ex)
{
_logger.Error(ex);
return View("Error");
}
finally
{
sw.Stop();
_logger.Info($"Index: {sw.ElapsedMilliseconds}ms");
}
}
本体2行のために、周りが10行。
これがアクションの数だけ増殖する。フィルターは、この「周り」を全部引き剥がすためのものです。
4種類のフィルターと実行順序
ASP.NET MVC のフィルターは、役割で4種類に分かれています。

大事なのは 実行順序 です。ここを絵にすると、こうなります。

認証 → アクション → 結果 の順で流れて、途中で例外が出たら例外フィルターに落ちる。この順番が、後でハマりポイントになります。
ここまでのポイント: フィルターは4種類。実行順序は「認証→アクション→結果」、例外は横から拾う。
実装: ログと実行時間をアクションフィルターで挟む
まず一番使うアクションフィルター。ActionFilterAttribute を継承して、前後のメソッドをオーバーライドします。
using System.Diagnostics;
using System.Web.Mvc;
public class LogActionFilter : ActionFilterAttribute
{
private const string Key = "__sw";
// アクションの直前に呼ばれる
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items[Key] = Stopwatch.StartNew();
var name = filterContext.ActionDescriptor.ActionName;
Trace.WriteLine($"[開始] {name}");
}
// アクションの直後に呼ばれる
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var sw = (Stopwatch)filterContext.HttpContext.Items[Key];
sw.Stop();
var name = filterContext.ActionDescriptor.ActionName;
Trace.WriteLine($"[終了] {name}: {sw.ElapsedMilliseconds}ms");
}
}
OnActionExecuting がアクションの前、OnActionExecuted が後。
これをコントローラかアクションに属性で付けるだけで、計測ロジックが本体から消えます。
[LogActionFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
var data = _service.GetData();
return View(data); // 計測もログも、もう書かなくていい
}
}
いい感じにコントローラが痩せましたよね。
例外フィルターと認証フィルター
例外ログは、IExceptionFilter に寄せます。
using System.Web.Mvc;
public class LogExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled) return;
Trace.WriteLine($"[例外] {filterContext.Exception}");
filterContext.Result = new ViewResult { ViewName = "Error" };
filterContext.ExceptionHandled = true; // ★ これを忘れると再スローされる
}
}
認証は、標準の AuthorizeAttribute を継承してチェックを差し込みます。
using System.Web;
using System.Web.Mvc;
public class ApiKeyFilter : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return httpContext.Request.Headers["X-Api-Key"] == "expected-key";
}
}
全アクションに効かせたいものは、FilterConfig でグローバル登録する。
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogExceptionFilter()); // 例外ログは全体で
filters.Add(new LogActionFilter()); // 計測も全体で
}
}
ここまでのポイント: 例外は IExceptionFilter、認証は AuthorizeAttribute、全体に効かせるならグローバル登録。
実行順序を誤解すると例外処理が効かない
ここが一番の詰まりどころです。フィルターを覚えたての頃に、俺も同じ勘違いをして半日つぶしました。
誤解1: 「例外フィルターを付ければ、どこの例外も全部拾える」
例外フィルターが拾うのは、基本的に アクション実行中 に投げられた例外です。実行順序が「認証→アクション→結果」なので、こうなります。
- 認証フィルターより前(ルーティングなど)の例外は拾わない
- 結果(View のレンダリング)中の例外は、状況によっては差し替えが間に合わない
「なんで例外フィルターで拾えないん??」の8割は、例外が出た場所が想定とズレてるパターン。順序の図(認証→アクション→結果)を思い出すと、拾える範囲が見えてきます。
誤解2: OnActionExecuted で「拾ったつもり」で再スローされる
OnActionExecuted には filterContext.Exception があって、アクションで出た例外を見られます。ここでログを吐いて「対処した」つもりでも——
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
Trace.WriteLine(filterContext.Exception.Message);
// ★ ExceptionHandled = true を立てないと、この後で例外が再スローされる
filterContext.ExceptionHandled = true;
}
}
filterContext.ExceptionHandled = true を立て忘れると、例外はそのまま上流に流れて、結局エラー画面(YSOD)に落ちる。
「ログは出てるのにエラー画面が出る」ときは、だいたいこれです。
ここまでのポイント: 例外フィルターが拾うのは主にアクション中の例外。握りつぶすなら ExceptionHandled = true を忘れない。
属性で個別に付ける vs グローバル登録
最後に使い分け。フィルターの効かせ方は2通りあります。
- 属性で付ける:
[LogActionFilter]をコントローラやアクションに直接。効く範囲が局所的で、コードを見れば「何が効いてるか」が分かる - グローバル登録:
FilterConfigで全アクションに。認証や例外ログみたいな「全体で漏らしたくない」ものはこっち
判断軸はシンプルで、「1個でも漏れたら困るか」。
ログイン確認や例外ログは漏れると事故るのでグローバル。特定画面だけの計測なら属性で十分です。
俺の現場ではこう使ってる
流通系の基幹の保守で、既存コントローラが「アクションごとにログイン確認コピペ」の状態だったことがあります。しかも数箇所、確認が抜けてた。
認証を1個のグローバルフィルターに寄せたら、抜け漏れが構造的に無くなって、コントローラも一気に痩せた。レビューで「ここ確認抜けてない?」の指摘がゼロになったのは、地味に効きました。
一点だけ注意。グローバルにしすぎると「どの画面に何が効いてるか」が読みにくくなります。
認証・例外みたいな全体もの以外は、属性で明示的に付けるほうが後任にやさしいです。
動作確認メモ: この記事のフィルタークラス(
System.Web.Mvc前提)はコンパイルと API の正しさを確認済みです。ただし「認証→アクション→結果」の実際の発火順序やエラー画面の差し替え挙動は、HTTP パイプラインを通す必要があるので、Windows + Visual Studio + .NET Framework の実機でお試しください。
まとめ
- アクションフィルターは、認証・例外・ログといった 横断的関心事 をアクションの前後に自動で挟む仕組み
- 4種類(認証・アクション・結果・例外)があり、実行順序は「認証→アクション→結果」、例外は横から拾う
- 例外フィルターが拾うのは主にアクション中の例外。順序を誤解すると「拾えない」が起きる
- 握りつぶすなら
ExceptionHandled = true。全体もの(認証・例外ログ)はグローバル、局所は属性
横断処理をフィルターに寄せると、コントローラは業務ロジックだけの薄い状態になります。1個直せば全体に効くので、保守がだいぶ楽になります!!こんな感じで、コントローラを薄く保てるのがフィルターの一番の旨みです。
よくある質問
Q1. ASP.NET MVC のフィルターの実行順序は?
認証フィルター(Authorization)が最初、次にアクションフィルターの OnActionExecuting、アクション本体、OnActionExecuted、続いて結果フィルター、View のレンダリングの順です。例外が出ると例外フィルターの OnException が呼ばれます。
Q2. アクションフィルターと例外フィルターの違いは?
アクションフィルター(IActionFilter)はアクションの前後で処理を挟み、ログや実行時間の計測に使います。例外フィルター(IExceptionFilter)は例外が発生したときだけ呼ばれ、例外のログ記録やエラー画面への差し替えに使います。
Q3. フィルターを全アクションに効かせるには?
FilterConfig の RegisterGlobalFilters でグローバルフィルターとして登録します。特定のコントローラやアクションだけに効かせたい場合は、属性としてクラスやメソッドに付けます。
Q4. OnActionExecuted で例外を握りつぶしたのに、エラー画面が出るのはなぜ?
filterContext.ExceptionHandled = true を立てていないからです。例外を見てログを吐いても、このフラグを立てないと例外は上流に再スローされ、エラー画面に落ちます。
Q5. 認証チェックはアクションフィルターに書いていい?
認証は認証フィルター(AuthorizeAttribute の継承など)に書くのが筋です。実行順序で認証がアクションより先に走るため、認証をアクションフィルターに書くと「アクションが始まってから弾く」ことになり、無駄な処理が走ります。
次に読むべき記事





以上!
執筆者
バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SIer の正社員からフリーランスに転じ、 複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る

