NancyFx-打造小型 WebAPI 與 Microservice 的輕巧利器
17 |
在做非網站系統整合時,我很愛用一招:寫個 Process 提供 WebAPI 介面給其他系統呼叫,不管你用什麼烏語言鬼平台,怎麼可能找不到 HttpCllient 元件或程式庫?都民國幾年了敢跟我說你不會寫Call 網頁的程式?你瞧瞧,多理直氣壯,乘著主流趨勢我們站上制高點,高舉 Web API 大旗, 天下無敵,哇哈哈哈~這招很棒吧?
不直接在 IIS 跑 ASP.NET,多半因為程式有執行身分權限或特殊限制(例如:整合Word、Excel等重量級互動式應用程式),通常我會寫成 Console Application 或 Windows Service,因此需要有 Self-Host 模組提供 TCP 連線、路由、Request/Response 處理管線等基礎建設。過去我研究過一些解決方案,例如:
- 自己從 TCP 寫起
MicroHttpServer - 用100行C#寫一個HTTP Server - 使用 ASP.NET Web API Self-Host
不用IIS也能執行ASP.NET Web API
從頭自已寫,純為好玩體驗一下還OK,要搞到順手好用的境界只怕要血流成河,且有重覆造輪子的重嫌。我在實務情境遇到許多小型 WebAPI,往往只有一到兩個 API,搬出功能強大的全套 ASP.NET Web API 跑 Self-Host 總給我有殺雞用牛刀的感覺,不符合 KISS (Keep It Simple, Stupid)中心思想。最近試玩過 NancyFx,驚為天人,簡直就是迷你 WebAPI 界的 Dapper!想必日後會在很多地方派上用場,特整理範例供日後參考。
訂個超簡單的題目:一個每次產生新 GUID 的 WebAPI。看看用 NancyFx 實做需要多少步驟、幾行程式碼。
首先我們開一個 Console Application 專案,用 NuGet 安裝 Nancy.Hosting.Self 套件(Nancy 也會一併安裝好 ):
宣告一個繼承 NancyModule 的 GuidGeneratorModule,建構式中為 Get["/"] 定義 Lambda 函式傳回 Guid.NewGuid().ToString()。這様 WebAPI 接 Request 的部分就寫完了,接著處理 Self-Hosting 的部分,在 Main() 裡建立一個 NancyHost,指定繫結的 URL,呼叫 Start() 就可以開門做生意了。提醒:Windows 7+需以管理者權限執行(較不建議)或使用 netsh http add urlacl url=http://+:32767/ user=machine\username 開放權限,參考。
using Nancy;
using Nancy.Hosting.Self;
using System;
namespace NancyFxDemo
{
class Program
{
static void Main(string[] args)
{
using (var host = new NancyHost(
new Uri("http://localhost:9527")))
{
host.Start();
Console.WriteLine("Press any key to stop...");
Console.Read();
host.Stop();
}
}
}
public class GuidGeneratorModule : NancyModule
{
public GuidGeneratorModule()
{
Get["/"] = (p) =>
{
return Guid.NewGuid().ToString();
};
}
}
}
薑薑薑薑~ 一不小心我們就把 WebAPI 寫完了!如此短小精悍,是我的菜沒錯 :P
最後補充一些常見應用:
1.由路由取得參數
//由URL路由取得參數
Get["route/{blah}"] = (p) =>
{
return "param=" + p.blah.ToString();
};
在路由使用{blah}標示參數,p.blah即為參數內容(或寫成 p["blah"] 也通)
2.定義 Get 及 Post,由 Form 及 QueryString 取得參數
//由QueryString或Form讀取參數
Get["concat"] = Post["concat"] = (p) =>
{
//Query, Form是dynamic,取屬性時可寫成Query.A或Query["A"]
//Query.Blah傳回型別為DynamicDictionaryValue, 有HasValue及Value
return string.Format("{0} {1}",
Request.Query.A.Value ?? Request.Form.A.Value ?? string.Empty,
Request.Query["B"].Value ?? Request.Form["B"].Value ?? string.Empty);
};
Nancy 的 Request 也有 Query 及 Form,不過不像 ASP.NET 可以用 Rquest["blah"] 通吃 QueryString、Form 及 Cookie,上面程式我示範用 ?? 運算子(學名為 Null 聯合運算子,Null-coalescing Operator)通吃 Query 及 Form。並同場加映如何讓 GET 及 POST 共用方法,
執行結果如下:
GET
POST
摁~ Pen Pineapple Apple Pen…
3.Model Binding
如果你覺得用 Request.Form/Query 取值太 Low,用 Model 才是王道,這也難不倒 Nancy。加上 using Nancy.ModelBinding 後,呼叫 this.Bind<T> 就可將參數 Bind 到預設定義好的 Model 型別物件上。
class ConcatParams
{
public string A { get; set; }
public string B { get; set; }
}
public GuidGeneratorModule()
{
Get["concatByModel"] = Post["concatByModel"] = (p) =>
{
var param = this.Bind<ConcatParams>();
return string.Format("{0} {1}", param.A, param.B);
};
}
4.POST JSON 及回傳 JSON
以下展示怎麼接收 POST 傳入的 JSON 內容以及傳回 JSON:
Post["concatByJson"] = (p) =>
{
var param = this.Bind<ConcateParams>();
return Response.AsJson(new
{
Resullt = string.Format("{0} {1}", param.A, param.B)
});
};
神奇的 Bind() 除了能從 Query 及 Form 對應屬性,還可直接將 POST 傳入的 JSON 字串反序列為物件,而 Response.AsJson() 則支援將物件序列化成 JSON 字串,沒花什麼力氣就輕鬆搞定。
除了以上的簡單應用,Nancy 也支援許多網站標準功能(例如:身分驗證、BeforeRequest/OnError事件、客製化錯誤頁面…),甚至能用 Razor 寫 View,要做出完整度不輸 IIS+ASP.NET MVC 的網站並非難事。但依我的策略,複雜的網站功能仍會選擇用 ASP.NET MVC 打正規戰,至於在 Console Application/WPF/Windows Service 加入 WebAPI 功能這類要近身肉博的場合,就仰賴 NancyFx 這把鋒利的輕巧短兵刃,用兩顆 DLL 加幾行程式秒殺需求。
Comments
# by Ike
「Request.Query.A.Value ??」是不是應為「Request.Query.A.HasValue ??」
# by Saint
黑大請教 如果需要先行驗證才能取資料,有建議的方式嗎?驗證可否也套b用到asp.net MVC中,謝謝
# by Jeffrey
to lke, Request.Query.A.Value 傳回結果有兩種可能:null 或參數字串,加上 ?? 可在傳回 null 時改用 Request.Form.A.Value。Request.Query.A.HasValue 為 boolean,傳回的是 true 或 false,應搭配 "?" 運算子使用。
# by Jeffrey
to Saint, NancyFx 可支援簡單 Basic 及 Forms 驗證,請參考:https://github.com/NancyFx/Nancy/wiki/Authentication-overview 。不過,我在設計這類獨立 Web API 主要是提供內部特定機器呼叫,多半會用 API Secret(某個約定好的密碼字串)配合鎖定來源 IP 做安控。
# by 風箏
這個範例的PORT號感覺有藏梗
# by Ike
哈,是我誤會了,以為黑大要用「Request.Query.A.HasValue ? Request.Form.A.Value : string.Empty」當例子
# by hl
hello,想问问最后一张图是什么软件界面?写过简单的web debug console,不过不想再造一个轮子。
# by Jeffrey
to hl, 文件測試使用的是一個強大 Chrome 插件 - PostMan https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop
# by JM
請問Nancy.Hosting.Self 跟直接用Kestrel有什麼差別嗎?
# by Jeffrey
to JM, NacyFx是一套Open Source的Web架構,比ASP.NET Core輕巧,在OWIN架構(http://blog.darkthread.net/post-2013-12-01-about-owin.aspx)下,Host/Server/MiddleWare/Application各層都可抽換組合,你說的Nancy.Hosting.Self跟Kestrel都是Host的選項,理論上NancyFx跑在Kestrel上,或ASP.NET Core跑在Nancy.Host.Self上都是可行的。
# by JM
感謝您的回覆,有些例子會用NancyFx + Kestrel,剛好您的例子是完全在Nancy上,小弟原本想是Kestrel是ASP.NET CORE內的Host,可以不用加元件,後來想到大大的例子是指一般的console(非指ASP.NET CORE)。
# by Saint
黑大好,時隔多日再向你請教,最近工作上有一需求,需等USER通知就開始發送mail並於指定的路徑下夾檔,便想到這個文章「不用IIS也能執行ASP.NET Web API」,再加上你這個NancyFx,總覺得可以利用這樣的方式來進行,如此,我應該是不用擔心time out的問題吧?還是你有更好的方式? 謝謝你
# by Jeffrey
to Saint, 關於timeout問題可以再補充說明嗎? 另外,推薦最近新發現,用 ASP.NET Core 寫還可以直接轉 Windows Service,也相當實用: https://blog.darkthread.net/blog/aspnetcore-run-as-service/
# by Saint
目前測試環境中己有一台IIS,怕把發mail這件事放在request中時,會發生time out 無法回應給請求者是否做完或是有意外發生,還是要設計為非同步的方式 ? 而不是在同一個請求中讓對方等我回應是否完成,謝謝
# by Jeffrey
to Saint, 如果是執行耗時的工作,確實不適合讓使用者在線上等回應,這種情境我習慣將寄信動作放入Queue(以檔案或資料庫儲存,如果遺失代價不高也可放記憶體就好),另外安排一支排程執行Queue裡的待處理作業。另外提供介面讓使用者查詢執行狀況或是出錯時主動通知。
# by Gower
請問 NancyFx 可以支援, 並限定 TLS 1.2(含以上) 連線嗎?
# by Jeffrey
to Gower,NancyFx 已停止開發一段時間,並於今年 5 月宣佈停止維護 ( https://www.facebook.com/darkthread.net/posts/2893551334106371 ),可以考慮改用 ASP.NET Core 取代,如果有 SSL 需求,可借用 IIS 或透過 Reverse Proxy。