原文: Nancy之Pipelines三兄弟(Before After OnError)
一、簡單描述
Before:如果傳回null,攔截器将主動權轉給路由;如果傳回Response對象,則路由不起作用。
After : 沒有傳回值,可以在這裡修改或替換目前的Response。
OnError : 傳回值與Before相似,引發的錯誤或異常時的控制代碼可以寫在這裡。
這三兄弟的大緻作用,看名字,也可以這樣簡單的了解:
Before:處理之前要幹的事。(傳回null,繼續處理;傳回Response對象,不再做要幹的那件事,換做Response對象要幹的事)
After : 處理之後要幹的事。
OnError : 處理出錯了要幹的事。
這三兄弟在NancyModule中的定義如下
1 public AfterPipeline After { get; set; }
2 public BeforePipeline Before { get; set; }
3 public ErrorPipeline OnError { get; set; }
而這三個Pipeline分别繼承了
AsyncNamedPipelineBase<TAsyncDelegate, TSyncDelegate>和NamedPipelineBase<TDelegate>
是以與他們有關的就主要包含在5個類中!具體的放在最後來看一下!
二、簡單用法
我們可以在Module中直接使用Before/After/OnError這三個
也可以在Bootstrapper中重寫RequestStartup或者ApplicationStartup來實作
當然也可以自定義,隻要實作IRequestStartup或者IApplicationStartup接口也可以完成相應的工作
下面我們就分别來說明一下
用法一:直接在Module中使用
定義一個BaseModule,具體如下:
1 public class BaseModule : NancyModule
2 {
3 public BaseModule()
4 {
5 //寫法一
6 Before += ctx => {
7 System.Diagnostics.Debug.WriteLine("BaseModule---Before");
8 return null;
9 };
10 After += ctx => {
11 System.Diagnostics.Debug.WriteLine("BaseModule---After");
12 };
13 OnError += (ctx, ex) => {
14 System.Diagnostics.Debug.WriteLine("BaseModule---OnError");
15 System.Diagnostics.Debug.WriteLine(ex.ToString());
16 return null;
17 };
18 //寫法二
19 //Before += MyBefore;
20 //After += MyAfter;
21 //OnError += MyOnError;
22 }
23 private Response MyBefore(NancyContext ctx)
24 {
25 System.Diagnostics.Debug.WriteLine("BaseModule---Before----寫法二");
26 return null;
27 }
28 private void MyAfter(NancyContext ctx)
29 {
30 System.Diagnostics.Debug.WriteLine("BaseModule---After----寫法二");
31 }
32 private Response MyOnError(NancyContext ctx, Exception ex)
33 {
34 System.Diagnostics.Debug.WriteLine("BaseModule---OnError----寫法二");
35 System.Diagnostics.Debug.WriteLine(ex.ToString());
36 return null;
37 }
38 }
在BaseModule中,用了兩種不同的形式來對Before、After、OnError進行處理,
都隻是列印出一些簡單的資訊,看這些輸出的資訊,可以幫助了解内部執行的順序!
可以看到,Before和OnError是Response類型的,After是void類型的
在這三兄弟的具體進行中,要根據實際情況來定(當然,你想就列印出一些東西也沒問題,畢竟我們還是可以把這些東西寫進日記嘛)!
下面定義一個HomeModule,具體如下:
1 public class HomeModule : BaseModule
2 {
3 public HomeModule()
4 {
5 Get["/"] = _ => "Catcher Wong";
6 Get["/err"] = _ => { throw new Exception("there're some errors"); };
7 }
8 }
其中,當我們通路http://localhost:port時,會顯示我們的文字,通路http://localhost:port/err時,會抛出我們設定異常!
運作起來,看看我們的Output(輸出)視窗
這是通路http://localhost:port時的情況

通路http://localhost:port/err時的情況
出現異常後并沒有去執行After!!執行完OnError之後就結束了。
同樣的,用寫法二也是如此!
基本一緻的效果。
用法二:在bootstrapper中重寫RequestStartup或者ApplicationStartup
先來看看重寫RequestStartup
1 public class Bootstrapper : DefaultNancyBootstrapper
2 {
3 protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
4 {
5 base.RequestStartup(container, pipelines, context);
6 pipelines.BeforeRequest += ctx => {
7 System.Diagnostics.Debug.WriteLine("Boorstrapper---RequestStartup---Before");
8 return null;
9 };
10 pipelines.AfterRequest += ctx => {
11 System.Diagnostics.Debug.WriteLine("Boorstrapper---RequestStartup---After");
12 };
13 pipelines.OnError += (ctx,ex) => {
14 System.Diagnostics.Debug.WriteLine("Boorstrapper---RequestStartup---OnError");
15 System.Diagnostics.Debug.WriteLine(ex.ToString());
16 return null;
17 };
18 }
19 }
我們同樣是輸出相應的資訊,運作前,把我們BaseModule中“三兄弟”的注釋掉
再來看看重寫ApplicationStartup
1 public class Bootstrapper : DefaultNancyBootstrapper
2 {
3 protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
4 {
5 base.ApplicationStartup(container, pipelines);
6 pipelines.BeforeRequest += MyBeforeRequest;
7 pipelines.AfterRequest += MyAfterRequest;
8 pipelines.OnError += MyOnErroe;
9 }
10 private Response MyBeforeRequest(NancyContext ctx)
11 {
12 System.Diagnostics.Debug.WriteLine("Boorstrapper---ApplicationStartup---Before");
13 return null;
14 }
15 private void MyAfterRequest(NancyContext ctx)
16 {
17 System.Diagnostics.Debug.WriteLine("Boorstrapper---ApplicationStartup---After");
18 }
19 private Response MyOnErroe(NancyContext ctx, Exception ex)
20 {
21 System.Diagnostics.Debug.WriteLine("Boorstrapper---ApplicationStartup---OnError");
22 System.Diagnostics.Debug.WriteLine(ex.ToString());
23 return null;
24 }
25 }
我們同樣是輸出相應的資訊,運作前,把我們BaseModule和RequestStartup中“三兄弟”的注釋掉
用法三:自定義用法(Nancy中有很多東西可以自定義,這個很靈活,很nice!)
下面來看看自定就要怎麼使用!
1 public class CustomRequest : IApplicationStartup
2 {
3 public void Initialize(IPipelines pipelines)
4 {
5 pipelines.BeforeRequest.AddItemToEndOfPipeline(ctx =>
6 {
7 System.Diagnostics.Debug.WriteLine("CustomRequest---IApplicationStartup---Before");
8 return null;
9 });
10 pipelines.AfterRequest.AddItemToEndOfPipeline(ctx =>
11 {
12 System.Diagnostics.Debug.WriteLine("CustomRequest---IApplicationStartup---After");
13 });
14 pipelines.OnError.AddItemToEndOfPipeline((ctx, ex) =>
15 {
16 System.Diagnostics.Debug.WriteLine("CustomRequest---IApplicationStartup---OnError");
17 System.Diagnostics.Debug.WriteLine(ex.ToString());
18 return null;
19 });
20 }
21 }
我們自定義一個CustomRequest讓它實作IApplicationStartup接口即可!
剩下的就是實作Before、After、OnError的處理!!
把之前的相關處理注釋掉,運作。
效果如下:
前面提到的,都是每種用法單獨的運作執行效果,那麼,每種用法的執行順序呢?下面來看看,把所有的注釋去掉,運作
現在是否很清晰呢?
Before 的執行順序 IApplicationStartup > ApplicationStartup > RequestStartup > BaseModule
OnError的執行順序 BaseModule > IApplicationStartup > ApplicationStartup > RequestStartup
再來看看After的執行順序
與OnError的處理順序一樣!!
三、内部實作的簡單分析
前面也提到了,這三兄弟的實作主要有這幾個類
BeforePipeline、AfterPipeline、ErrorPipeline以及抽象類NamedPipelineBase、AsyncNamedPipelineBase
NancyModule中也有相應的Before、After、OnError定義!
先來看看BeforePipeline吧
BeforePipeline是實作了AsyncNamedPipelineBase這個抽象類
裡面有用到 implicit operator ,不熟悉的可以參考
implicit (C# Reference)有一個重寫的Wrap方法,用于把同步的包裝成異步的形式
1 protected override PipelineItem<Func<NancyContext, CancellationToken, Task<Response>>> Wrap(PipelineItem<Func<NancyContext, Response>> pipelineItem)
2 {
3 var syncDelegate = pipelineItem.Delegate;
4 Func<NancyContext, CancellationToken, Task<Response>> asyncDelegate = (ctx, ct) =>
5 {
6 var tcs = new TaskCompletionSource<Response>();
7 try
8 {
9 var result = syncDelegate.Invoke(ctx);
10 tcs.SetResult(result);
11 }
12 catch (Exception e)
13 {
14 tcs.SetException(e);
15 }
16 return tcs.Task;
17 };
18 return new PipelineItem<Func<NancyContext, CancellationToken, Task<Response>>>(pipelineItem.Name, asyncDelegate);
19 }
其他的大緻都可以總結成下面這句代碼:
pipeline.AddItemToEndOfPipeline(xxxx);
把xxxx添加到管道中的末尾去。
同樣的,AfterPipeline與ErrorPipeline也是相類似的,
不同的是ErrorPipeline實作的是NamedPipelineBase這個抽象類,
沒有那個Wrap方法,多了一個dynamic的Invoke方法
1 public dynamic Invoke(NancyContext context, Exception ex)
2 {
3 dynamic returnValue = null;
4 using (var enumerator = this.PipelineDelegates.GetEnumerator())
5 {
6 while (returnValue == null && enumerator.MoveNext())
7 {
8 returnValue = enumerator.Current.Invoke(context, ex);
9 }
10 }
11 return returnValue;
12 }
這個Invoke方法的作用是:依次調用每個管道項目,直到有管道項目被傳回或者所有管道項目都已經被調用了!
兩個NamePipelineBase(同步和異步)都定義了一個pipelineItems(要執行的管道項目集合)
還有衆多虛方法!!大部分是插入的,還有一個删除的。
其中插入可分為在Pipeline的開始和結尾插入,以及是否要替換已存在的同名的Pipeline
下面的是比較重要的一個方法InsertItemAtPipelineIndex
同步的
1 public virtual void InsertItemAtPipelineIndex(int index, PipelineItem<TDelegate> item, bool replaceInPlace = false)
2 {
3 var existingIndex = this.RemoveByName(item.Name);
4 var newIndex = (replaceInPlace && existingIndex != -1) ? existingIndex : index;
5 this.pipelineItems.Insert(newIndex, item);
6 }
異步的
1 public virtual void InsertItemAtPipelineIndex(int index, PipelineItem<TAsyncDelegate> item, bool replaceInPlace = false)
2 {
3 var existingIndex = this.RemoveByName(item.Name);
4 var newIndex = (replaceInPlace && existingIndex != -1) ? existingIndex : index;
5 this.pipelineItems.Insert(newIndex, item);
6 }
這個方法的主要作用是将item插入到Pipeline的指定位置!(同步和異步的都有相應的實作!!不同的是item的類型而已!)
内部實作,簡單點的說法就是:就把我們寫的東西添加進Pipline去處理
最後來看看我們在Bootstrapper和自定義用到的IPipelines
1 public interface IPipelines
2 {
3 BeforePipeline BeforeRequest { get; set; }
4 AfterPipeline AfterRequest { get; set; }
5 ErrorPipeline OnError { get; set; }
6 }
十分簡單的定義!