當需要在用戶端建構 REST API,目標只能是.NET Framework 4 時,我們可以選擇 Nancy,搭配 Nancy Self-Host 自我掛載在 Console App,再加上 Topshelf 就可以輕易的把 Console App 變成 Windows Service。
Nancy 是一个的輕量級 Http 的服務框架,全名 NancyFx,它的靈感來自于Ruby 的 Sinatra 框架,其作者名字叫Frank Sinatra, NancyFx 名字中的 Nancy 是 Frank Sinatra 女兒的名字,Fx 的意思是 Framework(框架)
本文連結
開發環境
VS 2019
.NET Framework 4.0
Nancy.Hosting.Self Version 1.4.1
Nancy.Validation.DataAnnotations Version 1.4.1
開一個 Console App 的專案
安裝以下兩個套件
Install-Package Nancy.Hosting.Self -Version 1.4.1
Install-Package Nancy.Validation.DataAnnotations -Version 1.4.1
編寫第一個 Nancy
定義 NancyModule
必要套件 Install-Package Nancy.Hosting.Self -Version 1.4.1
宣告一個繼承 NancyModule 的 GeneratorNancyModule (Web API 的 ApiControllerController 很像),在建構函數宣告
1.Route:this.Get["guid"]
2.Action:要做的事
public class GeneratorNancyModule : NancyModule { public GeneratorNancyModule() { this.Get["guid"] = p => Guid.NewGuid().ToString(); } }
NancyHost 掛載服務
最後用 NancyHost.Start 把服務掛載起來,NancyHost.Stop 則是停止服務
internal class Program { private static void Main(string[] args) { var baseUri = new Uri("http://localhost:9527"); using (var host = new NancyHost(baseUri)) { host.Start(); Console.WriteLine("Service created"); Console.WriteLine("Press any key to stop..."); Console.Read(); host.Stop(); } } }
運行時出現了以下錯誤,我們得先解決這個問題
綁定 URL
- Win7 以上要使用 OWIN、Sefl Host,需使用管理員身分執行,才能使用不存在的 URL,簡單的說就是用管理員權限執行 Console App
- 使用已存在的URL保留區(不須管理員身分)
異動 URL 保留區
以管理者權限執行命令提示字元
新增
netsh http add urlacl url=http://+:9527/ user=machine\username
netsh http add urlacl url="http://+:9527/" user="Everyone"
刪除
netsh http delete urlacl url="http://+:9527/"
查看 URL 保留區
netsh http show urlacl
解決上面的問題之後,就能正常地訪問服務
Route
Nancy 中的路由是定義在 NancyModule 的建構函數,你需要定義以下四個部分
- 方法(Http Method)
- 模板(Pattern)
- 方法(Action)
- 條件約束(Condition)
i.e
public class ProductsModule : NancyModule { public ProductsModule() { Get["/products/{id}"] = _ => { //do something }; } }
or async:
public class ProductsModule : NancyModule { public ProductsModule() { Get["/products/{id}", runAsync: true] = async (_, token) => { //do something long and tedious }; } }
上述來自官網:https://github.com/NancyFx/Nancy/wiki/Defining-routes
Pattern
模式是用來響應應用程式的 URL,以下是 Nancy 所提供的模式
Literal segments - (10,000)
純文字
例:/person/name
Capture segments - (1,000)
帶有參數,最後我們可以用 dynamic 取得這個參數,參考 #Route 取得參數
例:/person/{name}
Capture segments (optional) - (1,000)
帶有參數,允許參數為空
例:/person/{name?}
Capture Segments (optional/default)
帶有參數,允許參數為空且有預設值
例:/person/{name?yao}
RegEx Segment - (1,000)
參數用正則表達式
例:/person/(?
Greedy Segment - (0)
模糊比對,可放在參數字尾
例:/person/{name*}
Greedy RegEx Segment - (100)
混合比對 RegEx and Greedy segment
例:^(?
Multiple Captures Segment - (100)
多個參數或是混合純文字
例:/{file}.{extension} or /{file}.ext
可針對整個類別定義一個 basic URL,有兩種方式
一種是呼叫建構函數,另一種是設定 ModulePath 屬性, ModulePath = "api/person"
或者針對 Action 指定 URL
public class PersonNancyModule : NancyModule { public PersonNancyModule() : base("api/person") { this.Get[""] = this.GetAllAction; this.Get["{id:int}"] = this.GetAction; this.Post[""] = this.InsertAction; this.Put["{id:int}"] = this.EditAction; this.Delete["{id:int}"] = this.DeleteAction; } ... private dynamic GetAllAction(dynamic arg) { throw new NotImplementedException(); } }
Action
Action 的寫法 可以用 LINQ Expression (Func<dynamic, dynamic>) 或是切出一個方法
LINQ 的寫法如下圖:
public class GeneratorNancyModule : NancyModule { public GeneratorNancyModule() { this.Get["guid"] = p => Guid.NewGuid().ToString(); } }
方法的寫法如下圖,參數跟回傳值都是 dynamic
public class PersonNancyModule : NancyModule { public PersonNancyModule() : base("api/person") { this.Get[""] = this.GetAllAction; } private dynamic GetAllAction(dynamic arg) { throw new NotImplementedException(); } }
Condition
條件約束(Condition)
可針對參數設定條件約束
例:/person/{id:int}
以下是 Nancy 提供的類型
類型 | 說明 |
---|---|
int | 只允許Int32的數字 |
long | 只允許Int64的數字 |
decimal | 只允許浮點數 |
guid | 只允許Guid |
bool | 只允許布林true/false |
alpha | 只允許英文字母 |
datetime | 只允許時間 |
datetime(format) | 只允許特定時間格式 |
min(mininum) | 只允許的最小整數 |
max(maxinum) | 只允許的最大整數 |
range(mininum, maxinum) | 只允許的整數範圍 |
minlength(length) | 只允許字串最小長度 |
maxlength(length) | 只允許字串最大長度 |
length(length) | 只允許的字串長度範圍 |
version | 只允許版本号,例1.0.0 |
Request
Route 取得參數
在 Route 定義了 {id},在 Action 裡面就可以使用 id 欄位
public class PersonNancyModule : NancyModule { public PersonNancyModule() : base("api/person") { this.Get["{id:int}"] = this.GetAction; } }
private dynamic GetAction(dynamic arg) { var id = arg.id != null ? (int) arg.id : default(int?); //TODO:Do thing return HttpStatusCode.OK; }
QueryString 取得參數
private dynamic GetAllAction(dynamic arg) { var id = this.Request.Query.id != null ? (int) this.Request.Query.id : default(int?); var name = this.Request.Query["name"] != null ? this.Request.Query["name"].ToString() : null; //TODO:Do thing return new {id, name}; }
執行結果如下:
Form 取得參數
private dynamic GetAllAction1(dynamic arg) { var id = this.Request.Form.id != null ? (int) this.Request.Form.id : default(int?); var name = this.Request.Form["name"] != null ? this.Request.Form["name"].ToString() : null; //TODO:Do thing return new {id, name}; }
執行結果如下:
Model Binding
將請求的 JSON JSON 反序列化變成物件
public class PersonNancyModule : NancyModule { public PersonNancyModule() : base("api/person") { this.Post[""] = this.InsertAction; } private dynamic InsertAction(dynamic parameters) { var source = this.Bind<Person>(); //TODO:Insert data return HttpStatusCode.OK; } internal class Person { public Guid Id { get; set; } [Required] [StringLength(20)] public string Name { get; set; } } }
Model Binding and Validate
必要套件 Install-Package Nancy.Validation.DataAnnotations -Version 1.4.1
除了資料綁定之外,還能驗證它,使用方式跟 ASP.NET Web API 也很像
1.BindAndValidate:反序列物件並驗證欄位
3.ModelValidationResult:存放驗證結果
2.ModelValidationResult.IsValid:判定是否通過驗證
private dynamic InsertAction1(dynamic parameters) { var source = this.BindAndValidate<Person>(); if (!this.ModelValidationResult.IsValid) { return this.ModelValidationResult; } //TODO:Insert data return HttpStatusCode.OK; }
驗證失敗得到的結果如下,可以看到返回一個物件,Nancy 也會幫我們序列化成 JSON
Response
根據類型回應給用戶不同的內容,常見的 JSON、Xml、File 可以使用 Response 屬性提供的擴充方法
或者是透過 Response 類別自訂回傳內容
private dynamic InsertAction(dynamic parameters) { var content = "{ Id: \"1\", Name: \"yao\" }"; var contentBytes = Encoding.UTF8.GetBytes(content); //TODO:Insert data return new Response { StatusCode = HttpStatusCode.OK, ContentType = "application/json", Contents = p => p.Write(contentBytes, 0, contentBytes.Length) }; }
範例位置
https://github.com/yaochangyu/sample.dotblog/tree/master/Nancy/Lab.ServiceNet4
延伸閱讀
使用 Topshelf 取代 Windows Service 專案
參考連結
NancyFx-打造小型 WebAPI 與 Microservice 的輕巧利器
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET