[Nancy] 通過 Nancy 建立 REST API

當需要在用戶端建構 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

  1. Win7 以上要使用 OWIN、Sefl Host,需使用管理員身分執行,才能使用不存在的 URL,簡單的說就是用管理員權限執行 Console App
  2. 使用已存在的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/(?[\d]{2,})

Greedy Segment - (0)

模糊比對,可放在參數字尾

例:/person/{name*}

Greedy RegEx Segment - (100) 

混合比對 RegEx and Greedy segment

例:^(?[a-z]{3,10}(?:/{1})(?[a-z]{5,10}))$

Multiple Captures Segment - (100)

多個參數或是混合純文字

例:/{file}.{extension} or /{file}.ext

後面的數字代表分數,假如匹配到多組 URL,則已分數最高的獲勝

 

可針對整個類別定義一個 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 專案

 

參考連結

Nancy(1)-快速建立簡單Http Server

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo