カスタムオブジェクトの定義について
JavaScriptのprototype周りを勉強しながら、過去・現在の自分で書いたコードを見直したところ、
ちょっとまずい構成をしていたことが判明しました。例として以下のコードで考えます。
//ストップウオッチ var StopWatch = function(){ var _st = undefined; var _ed = undefined; var _start = function(){ _st = new Date(); } var _stop = function(){ _ed = new Date(); } var _clear = function(){ _st = undefined; _ed = undefined; } var _getElapsedMilliseconds = function(){ if(_st === undefined || _st === undefined) return -1; return _ed.getTime() - _st.getTime(); } return { start : _start, stop : _stop, clear : _clear, getElapsedMilliseconds : _getElapsedMilliseconds } }
1) まずい点 - new演算子
まずい点というよりは、理解不足による無駄な処理を行っていた点です。
StopWatchコンストラクタ関数の戻り値が存在します。
この時の「return { ... }」 は return new Object();」 と等価です。
ということは、関数を(new関係なく)呼び出すだけでオブジェクト(インスタンス)が生成できてしまうのです。*1
あと、これはコンストラクタ関数じゃないので、createStopWatch()みたいな感じに名称変更も行うことも必要になるでしょう。
//等価 var sw1 = new StopWatch(); var sw2 = StopWatch();
2) まずい点 - オブジェクト(インスタンス)のサイズ
各オブジェクト(インスタンス)が全メンバとメソッドを持っているので、大量にオブジェクト(インスタンス)を生成するとメモリ量が圧迫される場合があります。
プロトタイプ拡張する形式でカスタムオブジェクト定義すれば、オブジェクト(インスタンス)では各自の状態だけを持たせるようにできるため、使用メモリ量が改善が期待できます。
3) まずい点 - オブジェクト(インスタンス)がカスタムオブジェクトでは無い
コンストラクタ関数の戻り値で、new Object()して、そこにプロパティをいろいろ設定したものをインスタンスとして使用しています。
これだとObjectとして認識されていまうようです。(StopWatchオブジェクトであることを期待していました。)
var sw = new StopWatch(); console.log(sw);
4) プロトタイプを意識したオブジェクト
定義例として以下のような記述が最も理解しやすいんじゃないかなと思います。
(ここを参考にしました)
//ストップウオッチ var StopWatch2 = (function(){ function StopWatch2(){ this._st = undefined; this._ed = undefined; } function _start(){ this._st = new Date(); } function _stop(){ this._ed = new Date(); } var _clear = function(){ this._st = undefined; this._ed = undefined; } var _getElapsedMilliseconds = function(){ if(this._st === undefined || this._ed === undefined) return -1; return this._ed.getTime() - this._st.getTime(); } StopWatch2.prototype = { constructor : StopWatch2, start : _start, stop : _stop, getElapsedMilliseconds : _getElapsedMilliseconds } return StopWatch2; })();
5) プロトタイプを意識したオブジェクト - メリット
- オブジェクト(インスタンス)サイズ量が小さくなる。オブジェクト共通で使うものはprototype経由で参照できる。
6) プロトタイプを意識したオブジェクト - デメリット
- メンバの隠蔽が出来ない。JavaScriptにアクセス修飾の概念が存在しない為。やろうと思うとクロージャを使う必要あり。
7) 定義方法の使い分け
一長一短ありますので、状況に応じて使い分けするのもよいかと思いますが、
常に「プロトタイプを意識したオブジェクト」の定義方法を採用するのでも問題ないかと思います。
メンバの隠蔽の件については、コーディング規約など、運用でカバーできますしね。。
NancyでSessionを扱う
ナンシーはデフォルトではSessionが無効になっています。
有効化するにはBootstrapperの初期化時に以下を呼び出します。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Session; namespace NancySessionSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { base.ApplicationStartup(container, pipelines); CookieBasedSessions.Enable(pipelines); } } }
使い方は今まで通り。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; namespace NancySessionSample { public class MyModule : NancyModule { public MyModule() : base() { Get["/"] = _ => { return "main"; }; Get["/aaa/{val}"] = p => { string val = p.val; Session["aaa"] = val; return "update session aaa"; }; Get["/bbb/{val}"] = p => { string val = p.val; Session["bbb"] = val; return "update session bbb"; }; Get["/dump"] = _ => { string aaa = Convert.ToString(Session["aaa"]); string bbb = Convert.ToString(Session["bbb"]); return string.Format("aaa:{0}, bbb:{1}", aaa,bbb); }; } } }
Nancyでのセキュリティー (CSRF)
ナンシーでのセキュリティ対策として、CSRFについて調査しました。
パッケージは Nancy.Hosting.Aspnet と Nancy.Viewengines.Razor を使いました。
ビュー - 登録フォーム (Views/form.cshtml)
ASP.NET MVCと同様に、チェック対象フォームにトークンを組み込みます。
このHTMLヘルパーはNancy.ViewEngines.Razor.HtmlHelpersクラスで定義されています。
<html> <head> <title>登録フォーム</title> </head> <body> <h1>個人情報を入力してください</h1> <form action="~/" method="post"> <p>名前:<input type="text" name="Namae" /></p> <p>年齢:<input type="text" name="Age" /></p> <p>カード番号:<input type="text" name="CardNo" /></p> <p><input type="submit" value="確認" /></p> @Html.AntiForgeryToken() </form> </body> </html>
ビュー - 登録完了ページ (Views/thanks.cshtml)
<html> <head> <title>登録しました</title> </head> <body> <h1>ありがとうございました</h1> </body> </html>
CSRFチェックを有効にする1
初期化時にNancy.Security.Csrf.Enable()します。
これでリクエスト毎のAfterPipeline処理でトークン作成が行われます。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Security; namespace NancyCsrfForm { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { base.ApplicationStartup(container, pipelines); Csrf.Enable(pipelines); pipelines.OnError.AddItemToEndOfPipeline((ctx, exc) => { return exc.Message; }); } } }
CSRFチェックを有効にする2
チェックを行いたいルート処理内で、ValidateCsrfToken()します。
チェック不合格で、CsrfValidationExceptionがthrowされます。
using System.Web; using Nancy; using Nancy.ModelBinding; using Nancy.Extensions; using Nancy.Security; using System.IO; namespace NancyCsrfForm { public class MyModule : NancyModule { public MyModule() : base() { Get["/"] = _ => { return View["form.cshtml"].WithContentType("text/html; charset=UTF-8"); }; Post["/"] = _ => { this.ValidateCsrfToken(); var model = this.Bind<FormModel>(); ViewBag.model = model; //todo: 入力チェック return Response.AsRedirect("~/regist"); }; Get["/regist"] = _ => { //todo: 登録処理 return View["thanks.cshtml"].WithContentType("text/html; charset=UTF-8"); }; } } public class FormModel { public string Namae { get; set; } public int Age { get; set; } public string CardNo { get; set; } public FormModel() { } } }
動作確認用ページ1
実際の悪さをするページ
<html> <head><title>CSRFページ</title></head> <body onload="document.attackform.submit()"> <form name="attackform" action="<登録フォームURL>" method="post"> <input type="text" name="Namae" value="攻撃太郎" /> <input type="text" name="Age" value="99" /> <input type="text" name="CardNo" value="9999-9999-9999-9999" /> <input type="submit" value="確認" /> </form> </body> </html>
動作確認用ページ2
本当は1*1なiframeを使ったりするのでしょうが、今回はわかるように大きくしています。
<html> <head><title>ダミーページ</title></head> <h1>ようこそいらっしゃいました!!</h1> <body> <iframe src="./attackform.html" width="500px" height="500px" /> </body> </html>
動作結果
エラー時は、チェック不合格種類を表示しています。
フォーム側のトークンが取得できなかったので、"TokenMissing"が出力されています。
Nancyメモ
ASP.NETアプリケーションとしてホストした場合の処理フローメモ
Nancy.Hosting.Aspnet.NancyHttpRequestHandler
- 静的オブジェクト。
- コンストラクタ内で呼び出すGetBootstrapper()で、
- web.configにnancyFxセクションが存在する場合は、そこからオブジェクトを生成する。 ⇒ Nancy-master\src\Nancy.Demo.Hosting.Aspnet\Web.config 参照
- NancyBootstrapperLocator経由でBootstrapperオブジェクトを取得する。
Nancy.Bootstrapper.NancyBootstrapperLocator
- Bootstrapperオブジェクトを生成する。
- Bootstrapperオブジェクトは静的オブジェクトとして保持している。
- NancyHttpRequestHandlerの静的コンストラクタで呼び出されている -> 一回だけ呼び出される。
- DefaultNancyBootstrapper以外でINancyBootstrapperを実装したクラスが存在する場合は、それを使用する。(存在しなければDefaultNancyBootstrapper)
Nancy.DefaultNancyBootstrapper
- 継承関係: INancyBootstrapper,INancyModuleCatalog -> NancyBootstrapperBase<T> -> NancyBootstrapperWithRequestContainerBase<T> -> DefaultNancyBootstrapper ※<T>にはTinyIoCContainerを指定
- Initialiseメソッド:
- NancyHttpRequestHandlerの静的コンストラクタで呼び出されている -> 一回だけ呼び出される。
- INancyBootstrapperで定義。
- Bootstrapper生成直後に呼び出しされる。 -> Nancy.Hosting.Aspnet.NancyHttpRequestHandlerのコンストラクタ内
- やってること
- アプリケーションコンテナオブジェクトの取得
- 対応型情報の取得1? -> DIするときの割り付け情報1?
- Conventionsの設定?
- 対応型情報の取得2? -> DIするときの割り付け情報2?
- 初期化 - GetApplicationRegistrationTasks()使用 -> なにやってるかはよくわからん。
- 初期化 - GetApplicationStartupTasks()使用 -> おそらくアプリケーション初期化処理。
- 初期化 - ApplicationStartup()呼び出し -> サブクラスでオーバーライドするやつ ※6と7の初期化の違いとして、DIできるかできないかの違いじゃないかなと思う。
- FavIconあり時に、アプリケーションパイプライン処理を追加する。
- 診断オブジェクトの初期化? -> IDiagnostics実装クラスの初期化処理。なにやってるかはよくわからん。
- ApplicationStartupメソッド:
- NancyBootstrapperBaseクラスで定義&ここでは空実装。
- NancyBootstrapperBase.Initialise()で呼び出ししている。
- RequestStartupメソッド:
- NancyBootstrapperBaseクラスで定義
- 呼び出し階層は GetEngine() -> InitializeRequestPipelines() -> RequestStartup() ※すべてブートストラッパークラス内のメソッド
- InitializeRequestPipelinesメソッド:
- パイプラインオブジェクトはは初期化時のものをベースに、リクエスト毎にクローンコピーしたものが使用される。
- 後述のRequestStartupで設定したパイプラインはそのリクエスト処理時のみ有効となる。
- また、コンテナもパイプラインと同様。
- ConfigureRequestContainerメソッド:
- NancyBootstrapperWithRequestContainerBaseクラスで定義&ここでは空実装。
- NancyBootstrapperWithRequestContainerBase.GetConfiguredRequestContainer()で呼び出している。-> GetConfiguredRequestContainer()呼び出しは、 GetAllModules(), GetModule(), InitializeRequestPipelines() で行われる。
- オーバーライドして何を実装することを想定されているのかが、よくわからない。
- ConfigureApplicationContainerメソッド:
- NancyBootstrapperBaseクラスで定義&ここでは空実装。
- NancyBootstrapperBase.Initialise()で呼び出ししている。
- オーバーライドして、INancyModuleCatalogとIRouteResolverの割り付けを行うことを想定しているようだ。
オーバーライドできるメソッドの呼び出され順は以下の通り
[初回アクセス時のみ呼び出し]
- ConfigureApplicationContainer(TContainer existingContainer)
- ConfigureConventions(NancyConventions nancyConventions)
- ApplicationStartup(TContainer container, IPipelines pipelines)
[リクエスト毎呼び出し]
- ConfigureRequestContainer(TContainer container, NancyContext context)
- RequestStartup()と同様に、InitializeRequestPipelines()内で呼び出されている処理なので、リクエスト毎に呼ばれる。
- RequestStartup(TContainer container, IPipelines pipelines, NancyContext context)
- NancyBootstrapperBase.GetEngine()で行っているRequestPipelinesFactoryの割り付け処理。ここでInitializeRequestPipelines()への参照(という表現で妥当かはわからんけど。)を渡している。RequestPipelinesFactoryはリクエスト毎に呼び出されている -> Nancy.NancyEngineのHandleRequestInternal()での this.RequestPipelinesFactory.Invoke(context);
NancyEngine
- リクエスト発生時にあらかじめ生成してあるNancyEngineオブジェクトを使ってリクエストをさばく -> NancyHttpRequestHandler.BeginProcessRequest()内
- NancyEngineはIoCコンテナによってコンストラクタパラメーターが生成されているっぽい。 -> この挙動を設定するには、BootstrapperのConfigureApplicationContainer()をオーバーライドし、コンテナの割り付け情報を変更する。
- HandleRequestメソッド:
- NancyContext生成
- プレリクエスト処理 -> 詳細はよくわからん
- 静的コンテンツ処理 -> 静的コンテンツへのアクセス時はここで処理して終了
- リクエストPipelines生成
- リクエストライフサイクル処理*1 -> InvokeRequestLifeCycle()
- BeforePipeLineによる処理 -> PipelinesのBeforeを使用
- Dispatcherによる処理 -> リクエストのルートに対する処理を行っていると思われる。
- AfterPipeLineによる処理 -> PipelinesのAfterを使用
- 各処理でエラー発生時には、PipelinesのOnErrorを使用したエラー処理が行われる。
ルート解決、ルート処理、レスポンス生成
- IRequestDispatcherのデフォルト実装であるDefaultRequestDispatcherによって行われる。
- クラスメンバ構成から想像するに、ルート解決 -> ルート処理 -> レスポンス生成 な流れでリクエスト処理を行っていると想像。
これ以降は次回へつづく?
*1:ここではタスクを組み立てるだけで、実際の処理は行っていない?
Nancyでのフォーム認証
1, Nancy.Authentication.Formsパッケージのインストール
2, IUserMapper実装クラスの作成
3, ログイン、ログアウトルートの実装
4, フォーム認証の設定
IUserMapper実装クラスの作成
IUserMapperインターフェイス、IUserIdentityインターフェイスの実装クラスを作成します。前者はログイン処理で入力されたユーザーID・パスワードのマッピング処理を、後者はユーザー識別情報を表します。NancyではユーザーIDをGUIDで表します。(そのようなNancyの仕様みたい)
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Authentication.Forms; using Nancy.Security; namespace NancyFormAuthSample { public class SampleUserInfo : IUserIdentity { public IEnumerable<string> Claims { get; set; } public string UserName { get; set; } } public class UserDatabase : IUserMapper { //ユーザー情報ストア。サンプルの為、staticなメンバを使用している。 private static List<Tuple<string, string, Guid>> users = new List<Tuple<string, string, Guid>>(); public UserDatabase() { users.Add(new Tuple<string, string, Guid>("admin", "password", Guid.NewGuid())); users.Add(new Tuple<string, string, Guid>("user", "password", Guid.NewGuid())); } public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) { var userRecord = users.Where(u => { return u.Item3 == identifier; }).FirstOrDefault(); return userRecord == null ? null : new SampleUserInfo() { UserName = userRecord.Item1 }; } //IUserMapperインターフェイスには含まれていないが、ここでユーザー認証処理を定義している。 //戻り値として、ログイン入力値に対応するユーザーID値を返す。 public static Guid? ValidateUser(string username, string password) { var userRecord = users.Where(u => { return u.Item1 == username && u.Item2 == password; }).FirstOrDefault(); if (userRecord == null) { return null; } return userRecord.Item3; } } }
ログイン、ログアウトルートの実装
以下サンプルソースの場合は、
/loginにPOSTリクエストし、ログインを行い、ログイン失敗したら/login?error=trueにリダイレクト。成功したら/secureへリダイレクトします。
/secureは認証済でないとアクセスできません。ログアウトするには/secure/logoutへアクセスします。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Authentication.Forms; using Nancy.Extensions; using Nancy.Security; namespace NancyFormAuthSample { public class MyModule : NancyModule { public IndexModule() : base("/login") { Get["/"] = _ => { //クエリ文字列は、Context.Request.Queryで取得する if (Context.Request.Query.error != null) { return Response.AsText("ユーザーIDまたはパスワードが不正です。", "text/plain; charset=UTF-8"); } else { return Response.AsText("ログインする際はPostリクエストしてください。", "text/plain; charset=UTF-8"); } }; Post["/"] = _ => { var userGuid = UserDatabase.ValidateUser(Context.Request.Form.Username, Context.Request.Form.Password); if (userGuid == null) { //リダイレクト先起点は、モジュールのルートパス起点ではないので注意すること。 return Context.GetRedirect("/login?error=true"); } DateTime? expiry = null; if (Request.Form.RememberMe.HasValue) { expiry = DateTime.Now.AddDays(7); } return Nancy.Authentication.Forms.ModuleExtensions.LoginAndRedirect(this, userGuid, expiry, "/secure"); }; } } public class SecureModule : NancyModule { public SecureModule() : base("/secure") { //このモジュールルートへのアクセスは認証済である必要あり。 this.RequiresAuthentication(); Get["/"] = _ => { string msg = string.Format("{0}さん、セキュアなページです。", Context.CurrentUser.UserName); return Response.AsText(msg, "text/plain; charset=UTF-8"); }; Get["/logout"] = _ => { return this.LogoutAndRedirect("/login"); }; } } }
フォーム認証の設定
Bootstrapperのサブクラスを作成し、フォーム認証設定を行います。
using Nancy; using Nancy.Authentication.Forms; using Nancy.Bootstrapper; using Nancy.TinyIoc; namespace NancyFormAuthSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context) { base.ConfigureRequestContainer(container, context); //TinyIoCContainerにIUserMapperの割り付けを登録 container.Register<IUserMapper, UserDatabase>(); } protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); var formsAuthConfiguration = new FormsAuthenticationConfiguration() { RedirectUrl = "/login", //認証失敗時のリダイレクト先 UserMapper = container.Resolve<IUserMapper>() }; FormsAuthentication.Enable(pipelines, formsAuthConfiguration); //フォーム認証の有効化 } } }
Nancyでの静的リソースアクセス
ナンシーで静的リソースへのアクセスを行うには3つの方法があります。
デフォルト設定に従う。
Nancy supports multiple static content conventions at once and is shipped with a default convention that will look for files in the /content path of your application.
https://github.com/NancyFx/Nancy/wiki/Managing-static-content
これが一番簡単な方法です。
/content 以下にリソースファイルを配置するだけです。これはサブディレクトにも適用されます。
リソースディレクトリのカスタム設定(ヘルパー使用)
StaticContentConventionBuilder.AddDirectory()を使って、リソースディレクトリの割り付けを変更しています。
namespace NancyStaticResourceSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { base.ApplicationStartup(container, pipelines); this.Conventions.StaticContentsConventions.Clear(); //contentフォルダの割り付け削除 this.Conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/aaa")); } } }
リソースディレクトリのカスタム設定
ヘルパークラスが生成するラムダ相当を自前で作成しています。
(以下サンプルはかなりアレなので、参考程度にとどめてください^^;)
namespace NancyStaticResourceSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ConfigureConventions(NancyConventions nancyConventions) { base.ConfigureConventions(nancyConventions); this.Conventions.StaticContentsConventions.Clear(); //contentフォルダの割り付け削除 nancyConventions.StaticContentsConventions.Add((content, physicalRootPath) => { var reqPath = content.Request.Path; if (reqPath.StartsWith("/aaa")) { var res = new Response(); res.Contents = (st) => { using (var src = new FileStream(System.IO.Path.Combine(physicalRootPath, @"aaa\image1.png"), FileMode.Open)) { src.Seek(0, SeekOrigin.Begin); for (var i = 0; i < src.Length; i++) st.WriteByte((byte)src.ReadByte()); } }; res.ContentType = "image/png"; return res; } return null; }); } } }
リソースディレクトリの無効化
静的リソースを扱わない場合は、リソースアクセス判定処理を無効化することで処理の高速化が見込めるでしょう。
namespace NancyStaticResourceSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { base.ApplicationStartup(container, pipelines); container.Register<IStaticContentProvider, DisabledStaticContentProvider>(); //静的リソース判定結果を無条件でfalseとするProviderをRegistする } } }
Nancyのトレース機能
ナンシーのトレース機能について調べてみました。
まずはBootstrapperのサブクラスを作成してトレース機能を有効にします。
using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; using Nancy.Diagnostics; namespace NanNan { public class MyBootstrapper : DefaultNancyBootstrapper { protected override Nancy.Diagnostics.DiagnosticsConfiguration DiagnosticsConfiguration { //ダッシュボードへログインする際のパスワードを設定する必要あり。 get { return new Nancy.Diagnostics.DiagnosticsConfiguration() { Password = @"Abcd@1234" }; } } protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, IPipelines pipelines) { //トレースを有効にする StaticConfiguration.EnableRequestTracing = true; } } }
次にモジュールにトレース処理を組み込みます。
これでモジュールの該当ルートへリクエストが発生する度にトレースされるようになります。
using Nancy; using System; namespace NanNan { public class MyModule : NancyModule { public IndexModule() { Get["/"] = _ => { Context.Trace.TraceLog.WriteLog((s) => { s.AppendFormat("ルートへのアクセス:{0}", System.DateTime.Now.Ticks.ToString()); }); return "hello, root"; }; Get["/aaa"] = _ => { Context.Trace.TraceLog.WriteLog((s) => { s.AppendFormat("aaaへのアクセス:{0}", System.DateTime.Now.Ticks.ToString()); }); return "hello, aaa"; }; } } }
トレースログを確認するには、ダッシュボードへアクセスします。
<アプリケーションURL>/_Nancy でアクセスすることができます。
1,ログインページ
2,ダッシュボードページ。トラッキングメニューを選びます。
3,ユーザーセッション一覧ページ(と思う)
4,セッション内アクセス一覧ページ
5,アクセス時情報出力ページ
ページ下部にトレース情報が出力されます。
全てのルートへのアクセスに対してトレースしたい場合は、Bootstrapperで設定することが可能。
また、パイプラインのOnErrorでエラー時トレースを行うことも可能。
using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; using Nancy.Diagnostics; namespace NanNan { public class MyBootstrapper : DefaultNancyBootstrapper { protected override Nancy.Diagnostics.DiagnosticsConfiguration DiagnosticsConfiguration { //ダッシュボードを有効にする get { return new Nancy.Diagnostics.DiagnosticsConfiguration() { Password = @"Tonkatu@1919" }; } } protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, IPipelines pipelines) { //トレースを有効にする StaticConfiguration.EnableRequestTracing = true; pipelines.BeforeRequest += (ctx) => { //Nullを返すことで、リクエスト処理を以降のパイプラインに任せる。 //ここで意味のあるレスポンスを返すと、リクエスト処理がこの時点で終了する。 ctx.Trace.TraceLog.WriteLog((s) => { s.AppendLine("ビフォーリクエスト!!"); }); return null; }; pipelines.AfterRequest += (ctx) => { ctx.Trace.TraceLog.WriteLog((s) => { s.AppendLine("アフターリクエスト!!"); }); }; pipelines.OnError += (ctx, err) => { ctx.Trace.TraceLog.WriteLog((s) => { s.AppendLine("エラーが発生:" + err.Message); }); //本当はエラーページのViewなどを返す。 return err.Message; }; } } }