天天看點

Nancy Web架構Nancy架構

原文 Nancy Web架構

  1. 安裝 Nancy項目模闆
  2. 建立

    Nancy Empty Web Application with ASP.NET Hosting

  3. 添加

    Nancy module

    ,它是一個标準C#類,通過添加下面幾行代碼定義了web應用的路由處理方法。
  4. 編譯并運作。
public class HelloModule : NancyModule
{
    public HelloModule()
    {
        Get["/"] = parameters => "Hello World";
    }
}
      

Module繼承自

NancyModule

類。Module是必不可少的.它不僅定義了路由,還提供了許多其他資訊,比如請求、上下文、構造響應的輔助方法、視圖渲染等等。

可以在任意地方定義module,比如外部的dll等,這為代碼的複用帶來很大的友善。不用擔心效率問題,掃描module隻在程式啟動時發生。

類似命名空間的概念,在建立構造方法時傳給base一個名稱。

public class ResourceModule : NancyModule
{
    public ResourceModule() : base("/products")
    {
        // would capture routes to /products/list sent as a GET request
        Get["/list"] = parameters => {
            return "The list of products";
        };
    }
}
      

路由是在module的構造方法中定義的。為了定義一個路由,你需要聲明

方法

+

模式

動作

+(可選)

條件

比如:

public class ProductsModule : NancyModule
{
    public ProductsModule()
    {
        Get["/products/{id}"] = _ =>
        {
            //do something
        };
    }
}
      

或者異步

public class ProductsModule : NancyModule
{
    public ProductsModule()
    {
        Get["/products/{id}", runAsync: true] = async (_, token) =>
        {
            //do something long and tedious
        };
    }
}
      

支援HTTP常見方法:

DELETE

,

GET

HEAD

OPTIONS

POST

PUT

PATCH

模式能夠自定義,Nancy提供了一些常用的:

  1. 字面量 -

    /some/literal/segments

  2. 捕獲片段 -

    /{name}

    ,擷取URL的片段,并傳給路由的Action
  3. 捕獲可選片段 -

    /{name?}

    ,添加了一個問号,片段就是可選的了
  4. 捕獲可選/預設片段 -

    /{name?default}

  5. 正則片段 -

    /(?<age>[\d]{1,2})

    ,使用命名捕獲組來捕獲片段,如果不需要捕獲,使用非捕獲組,比如

    (?:regex-goes-here)

  6. 貪心片段 -

    /{name*}

    ,從/處開始捕獲
  7. 貪心正則捕獲 -

    ^(?<name>[a-z]{3, 10}(?:/{1})(?<action>[a-z]{5, 10}))$

  8. 多個捕獲片段 -

    /{file}.{extension}

    或者

    /{file}.ext

動作時一個lambda表達式

Func<dynamic, dynamic>

,輸入時

DynamicDictionary

,詳見

此處

.

響應可以使任意的model,最終的結果會被

Content Negotiation

處理。但是如果傳回值是

Response

類型,則原樣傳回。

Response

對象有幾個隐形轉換操作: 1.

int

變為Http的狀态 2.

HttpStatusCode

枚舉值 3.

string

直接是相應的body 4.

Action<Stream>

則寫道response stream中

路由條件用來過濾(比如登入非登入)。使用

Func<NancyContext, bool>

的lambda表達式定義.

Post["/login", (ctx) => ctx.Request.Form.remember] = _ => 
{
     return "Handling code when remember is true!";
}

Post["/login", (ctx) => !ctx.Request.Form.remember] = _ => 
{
     return "Handling code when remember is false!";
}
      

Get["/intConstraint/{value:int}"] = _ => "Value " + _.value + " is an integer.";
      

隻有為int的才會比對。

限制: -

int

-

decimal

guid

bool

alpha

datetime

datetime(format)

min(minimum)

max(maximum)

range(minimum, maximum)

minlength(length)

maxlength(length)

length(minimum, maximum)

實作

IRouteSegmentConstraint

接口,或者繼承自 -

RouteSegmentConstraintBase<T>

- Base class for a named constraint. -

ParameterizedRouteSegmentConstraintBase<T>

- Base class for a named constraint that accepts arguments.

一個email限制

public class EmailRouteSegmentConstraint : RouteSegmentConstraintBase<string>
{
    public override string Name
    {
        get { return "email"; }
    }

    protected override bool TryMatch(string constraint, string segment, out string matchedValue)
    {
        if (segment.Contains("@"))
        {
            matchedValue = segment;
            return true;
        }

        matchedValue = null;
        return false;
    }
}
      

用法

Get["/profile/{value:email}"] = _ => "Value " + _.value + " is an e-mail address.";
      

一個請求有時符合多個模式,此時記住: 1. module的順序在啟動時不定 2. 同一module中的路由是按順序來的 3. 多個比對中,得分最高的比對 4. 得分相同的比對按照啟動時的順序比對

一些可能的用法:

// would capture routes like /hello/nancy sent as a GET request
Get["/hello/{name}"] = parameters => {
    return "Hello " + parameters.name;
};

// would capture routes like /favoriteNumber/1234, but not /favoriteNumber/asdf as a GET request
Get["/favoriteNumber/{value:int}"] = parameters => {
    return "So your favorite number is " + parameters.value + "?";
};

// would capture routes like /products/1034 sent as a DELETE request
Delete[@"/products/(?<id>[\d]{1,7})"] = parameters => {
    return 200;
};

// would capture routes like /users/192/add/moderator sent as a POST request
Post["/users/{id}/add/{category}"] = parameters => {
    return HttpStatusCode.OK;
};
      

http://www.philliphaydon.com/2013/04/nancyfx-implementing-your-own-routing/

Before/After管道、主路由委托都可以使用async.文法絕大部分與同步代碼一緻,但需要注意下面的變化:

  • before/after鈎子接受兩個參數,context和cancellation token(取消令牌),而不僅僅是context
  • 路由定義有一個附加的bool參數,并且委托接受兩個參數,一個捕獲的參數,另一個cancellation token.

public MainModule()
{
    Before += async (ctx, ct) =>
        {
            this.AddToLog("Before Hook Delay\n");
            await Task.Delay(5000);

            return null;
        };

    After += async (ctx, ct) =>
        {
            this.AddToLog("After Hook Delay\n");
            await Task.Delay(5000);
            this.AddToLog("After Hook Complete\n");

            ctx.Response = this.GetLog();
        };

    Get["/", true] = async (x, ct) =>
        {
            this.AddToLog("Delay 1\n");
            await Task.Delay(1000);

            this.AddToLog("Delay 2\n");
            await Task.Delay(1000);

            this.AddToLog("Executing async http client\n");
            var client = new HttpClient();
            var res = await client.GetAsync("http://nancyfx.org");
            var content = await res.Content.ReadAsStringAsync();

            this.AddToLog("Response: " + content.Split('\n')[0] + "\n");

            return (Response)this.GetLog();
        };
}
      

DynamicDictionary

類似字典,但功能更多.從請求中擷取的值都儲存到它裡面。可以使用屬性或者index來使用捕獲的值。

Get["/hello/{name}"] = parameters => {
    return "Hello " + parameters.name;
};

Get["/goodbye/{name}"] = parameters => {
    return "Goodbye " + parameters["name"];
};
      

存儲的值可以顯示或者隐式的轉換為基礎類型或者特殊屬性.使用

HasValue

決定是否被指派。值已經實作了

IEquatable<>

IConvertible

接口。

除了為特定的路由定義處理程式,module還可以攔截比對某個路由的請求,請求前後都能做到。重要的是要了解,隻有傳入的請求比對子產品的路由之一,這些攔截器才會被調用。

Before攔截器能讓你修改請求,甚至可以通過傳回一個response來放棄請求。

Before += ctx => {
    return <null or a Response object>;
};
      

定義Before攔截器的文法與定義路由有些不同。因為它是定義在module上,被所有路由調用,是以不需要比對模式。

傳給攔截器的是目前請求的NancyContext執行個體。

最後的不同就是攔截器的傳回值,如果傳回

null

,攔截器将主動權轉給路由;如果傳回

Response

對象,則路由不起作用。

與定義Before爛機器相同,但是沒有傳回值。

After += ctx => {
    // Modify ctx.Response
};
      

Before攔截器可以修改Request,相應的,After攔截器可以修改Response。

應用管道能在所有的路由上執行,是全局性的。

應用級的

Before

鈎子通過

Func<NancyContext, Response>

函數定義:

pipelines.BeforeRequest += (ctx) => {
    return <null or a Response object>;
};
      

異步版本的:

pipelines.BeforeRequest += async (ctx, token) => {
    return <null or a Response object>;
};
      

After攔截器通過`Action定義:

pipelines.AfterRequest += (ctx) => {
    // Modify ctx.Response
};
      

OnError

攔截器用來攔截路由發生的錯誤。通過它可以擷取

NancyContext

和發生的異常。

OnError

攔截器通過

Func<NancyContext, Exception, Response>

pipelines.OnError += (ctx, ex) => {
    return null;
};
      

System.AggregateExceptions在OnError管道中的注意事項:

路由是通過許多嵌套的Task(

System.Threading.Tasks.Task

)來執行的。如果那個任務出現了問題,異常會被包裝到

System.AggregateException

System.AggregateException

可以持有任意個異常。

如果隻有一個異常,Nancy會解包異常并且交給

OnError

管道。如果發生多個異常,Nancy會使用

System.AggregateException

,以避免吞異常。

Bootstrapper

中建立系統級的鈎子.可以在

ApplicationStartup

RequestStartup

方法中定義它們。這是因為也許你需要在鈎子中使用容器中的一些東西。兩個方法的不同之處在于範圍不同。

protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
}

protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
}
      

通過使用

pipelines

中适當的屬性來建立鈎子。它允許你擷取

BeforeRequest

AfterRequest

OnError

屬性。

發送資料給Nancy可以有多種方法,比如Query String, 路由捕獲參數、請求體request body。手工處理這些不同的方法也可以,但是還有一種方法就是統一處理,綁定到

model

Nancy隻用一行代碼就能處理上述的所有情況,并且能接受

JSON

XML

形式的請求。

也可以擴充Nancy的模型綁定。

Nancy的模型綁定在

NancyModule

中被定義為一個單獨的擴充方法。該擴充在

Nancy.ModelBinding

命名空間裡,并且添加了Bind()和BindTo()方法

Foo f = this.Bind();

var f = this.Bind<Foo>();

var f = this.BindTo(instance);
      

上面3個有着相同的功能,他們提供了做同一事物的不同方法。前兩個使用Bind()重載來建立

Foo

類型的執行個體,并且綁定;BindTo()則綁定到現有執行個體。

var f = this.Bind<Foo>(f => f.id, f => f.creator, f => f.createddate);
      
var f = this.Bind<Foo>("id", "creator", "createddate");
      

當綁定到到arrary, list或者ienumerable時,屏蔽的是序列中的元素。

使用

BindingConfig

執行個體來修改model binder的預設行為。

下面是

BindingConfig

提供的一些配置項:

屬性 描述 預設
BodyOnly 是否隻綁定request body。這種情況下,request和context參數都不會被綁定。如果沒有body并且沒有選項,那麼綁定就不會放生 false
IgnoreErrors 是否忽略綁定錯誤并且繼續下一個屬性
Overwrite 丙丁是否可以覆寫沒有預設值的屬性 true

不準Overwrite還有一個快捷方法:

BindingConfig.NoOverwrite

有時你像在請求中發送結構化的資料,比如

JSON

XML

,并且綁定到模型。模型綁定器支援這種反序列化。

Nancy支援兩種反序列化:JSON和XML。綁定器根據Http的

Content-type

頭來決定使用哪一種反序列化。

預設使用JSON反序列化來處理

application/json

text/json

application/vnd....+json

。同樣的使用XML反序列化來處理

application/xml

text/xml

application/vnd....+xml

對于其他模型綁定器,你可以使用自己的反序列化,并且Nancy會自動檢測他們,任何使用者定義的綁定器的優先級都高于内建的。 注意:如果你使用Nancy.Json.JsonSetting.MaxJsonLength Exceeded錯誤,那是因為你的payloads太高了,在Bootstrapper中更改限制:

ApplicationStartup

中設定

Nancy.Json.JsonSettings.MaxJsonLength=int.MaxValue

要綁定複選框到bool值,确定設定

value=true

<input type="checkbox" name="rememberMe" value="true"/>
      
public class LoginModel
{
    public bool RememberMe { get; set; }
}
      

如果有一個form:

<form action="/ArrayOnObject" method="post">
  <input type="text" name="Tags" value="Tag1,Tag2,Tag3"/>
  <input type="text" name="Ints" value="1,2,3,4,4,5,6,3,2,21,1"/>
  <input type="submit" value="Submit"/>
</form>
      

而且有一個類:

public class Posts
{
  public string[] Tags { get; set; }
  public int[] Ints { get; set; }
}
      

使用一個簡單的語句:

var listOfPosts = this.Bind<Posts>();
      

<form action="/SimpleListDemo" method="post">
      User 1:<input type="text" name="Name[0]" value="thecodejunkie" /> 
      Commits <input type="text" name="Commits[0]" value="1068"/>
      <br />
      User 2:<input type="text" name="Name[1]" value="grumpydev" />  
      Commits <input type="text" name="Commits[1]" value="1049"/>
      <br />
      User 3:<input type="text" name="Name[2]" value="jchannon" />  
      Commits <input type="text" name="Commits[2]" value="109"/>
      <br />
      User 4:<input type="text" name="Name[3]" value="prabirshrestha" />  
      Commits <input type="text" name="Commits[3]" value="75"/>
      <br />
      User 5:<input type="text" name="Name[4]" value="phillip-haydon" />  
      Commits <input type="text" name="Commits[4]" value="40"/>
      <br />
      <input type="submit" value="Test the binding thingy"/>
</form>
      

可以使用

this.Bind<List<User>>();

來綁定對象清單:

public class User
{
   public string Name { get; set; }
   public int Commits { get; set; }
}
      

兩種分隔符

  • 下劃線(

    Name_1

    Name_2

    等)
  • 括号(

    Name[1]

    Name[2]

bootstrapper負責自動發現模型、自定義模型綁定、依賴等等。可以被替換掉。

public class CustomBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
    {
         // your customization goes here
    }
}
      

應用啟動時,它會尋找自定義的bootstrap,如果沒有找到,則使用

DefaultNancyBootstrap

。每個應用隻能有一個bootstrapper. 如果有多個,則Nancy尋找最底層的bootstrapper。

注入自己的依賴到NancyModule中

public class Home : NancyModule
{
    public Home(IMessageService service)
    {
        //If there is only one implementation of IMessageService in the application,
        // TinyIoC will resolve the dependency on its own and inject it in the module.
    }
}
      

視圖引擎就是輸入“模闆”和“模型”,輸出HTML(大部分情況下)到浏覽器。

Nancy預設使用

SuperSimpleViewEngine

。它支援一些必要的功能:layout布局、partials部分、models模型、conditions條件和iterations循環。你可以使用這個而不無需其他依賴。它支援

.html

.sshtml

檔案。

@Master['MasterPage']

@Section['Content']
    <p>This content from the index page<p>
    <h3>Partials</h3>
    <p>Login box below rendered via a partial view with no model.</p>
    <div id="login">
        @Partial['login'];
    </div>
    <p>Box below is rendered via a partial with a sub-model passed in.</p>
    <p>The submodel is a list which the partial iterates over with Each</p>
    <div id="users">
        @Partial['user', Model.Users];
    </div>
    <h3>Encoding</h3>
    <p>Model output can also be encoded:</p>
    <p>@!Model.NaughtyStuff</p>
@EndSection
      

除此之外,Nancy還支援Razor, Spark, NDjango和dotLiquid引擎。通過添加引用,Nancy會自動的根據檔案字尾名調用對應的引擎。

Get["/products"] = parameters => {
    return View["products.html", someModel];
};
      

模闆說明: 1. 視圖檔案名: "products.html" 2. 如果沒有字尾,而且有多個同名模闆,則會收到

AmbigiousViewsException

錯誤。 3. 一個相對于跟的路徑(比如:

products/products.html

)

更多參見

視圖位置約定

如果值傳遞給View一個模型,Nancy會用模型名(去掉"Model"字尾)作為視圖名。

Get["/products"] = parameters => {
    return View[new ProductsModel()];
};
      

如果找不到,就會報406 Not Acceptable.

SSVE基于正則,支援

sshtml

html

html

檔案字尾。

模型可以是标準類型,或者

ExpandoObjects

(或者實作了

IDynamicMetaObjectProvider

實作了

IDictionary<string, object>

的對象)。

所有的指令都可以有分号,但不是必須的。

[.Parameters]

這樣的參數可以使任意層級的,比如

This.Property.That.Property

注意:所有引号都是_單引号_.

如果變量不能替換,則使用

[Err!]

替換。

文法:

@Model[.Parameters]
      

例子:

Hello @Model.Name, your age is @Model.User.Age
      

循環不能嵌套

@Each[.Parameters]
   [@Current[.Parameters]]
@EndEach
      

@Each

表示循環;

@Current

表示目前變量,使用方法同

@Model

@Each.Users
   Hello @Current.Name!
@EndEach
      

參數必須是bool,或能隐式轉化。嵌套的@If @IfNot不支援。

@If[Not].Parameters
   [contents]
@EndIf
      
@IfNot.HasUsers
   No users found!
@EndIf
      

如果module實作了

ICollection

,那你就能使用隐式轉換。使用

Has

字首。

Has[CollectionPropertyName]
      
@If.HasUsers
   Users found!
@EndIf
      

@Model

@Current

都可以有一個

!

,用來編碼HTML:

@!Model[.Parameter]
@!Current[.Parameter]
      
@!Model.Test

@Each
   @!Current.Test
@EndEach
      

@Partial['<view name>'[, Model.Property]]
      
// Renders the partial view with the same model as the parent
@Partial['subview.sshtml'];

// Renders the partial view using the User as the model
@Partial['subview.sshtml', Model.User];
      

可以聲明master頁和節。不必為每個節提供内容。Master能用

@Module

,并且擴充名可以省略。

可以多次使用

@Section

文法

@Master['<name>']

@Section['<name>']
@EndSection
      
// master.sshtml
<html>
<body>
@Section['Content'];
</body>
</html>

// index.sshtml
@Master['master.sshtml']

@Section['Content']
   This is content on the index page
@EndSection
      

防止CSRF

@AntiForgeryToken
      
@AntiForgeryToken
      

擴充相對路徑為整體路徑。

@Path['<relative-path>']
      
@Path['~/relative/url/image.png']
      

這個Razor引擎跟ASP.NET MVC的有點不一樣。

注意,Nancy仍然綁定模型到

@Model

,而不是ASP.NET中的

@model

隻需要添加

Nancy.ViewEngines.Razor.dll

(使用nuget安裝

Nancy.ViewEngines.Razor

)。然後試圖模闆以

cshtml

vbhtml

結尾即可。

視圖位置的約定通過

Func<string, dynamic, ViewLocationContext, string>

方法以及下面的一些預設約定來定義。

(viewName, model, viewLocationContext) => {
    return viewName;
}
      

這個約定會在根目錄裡尋找視圖。但是如果視圖包含一個相對路徑,視圖名稱執行對應于根路徑的路徑。比如,視圖

admin/index

會在

admin/index

目下尋找視圖。

(viewName, model, viewLocationContext) => {
    return string.Concat("views/", viewName);
}
      

很簡單,視圖

admin/index

views/admin/index

下查找對應的視圖。

(viewName, model, viewLocationContext) => {
    return string.Concat("views/", viewLocationContext.ModulePath, "/", viewName);
}
      

對于子產品products的視圖

admin/index

,會在

views/products/admin/index

中查找視圖。

(viewName, model, viewLocationContext) => {
    return string.Concat(viewLocationContext.ModulePath, "/", viewName);
}
      

這個約定會在與子產品名相同的檔案夾中查找視圖。

(viewName, model, viewLocationContext) => {
    return string.Concat(viewLocationContext.ModuleName, "/", viewName);
}
      

查找以子產品名為字首的對應視圖。

(viewName, model, viewLocationContext) => {
    return string.Concat("views/", viewLocationContext.ModuleName, "/", viewName);
}
      

查找views檔案夾下以子產品名為字首的對應視圖。

如果沒有提供視圖名而隻提供了視圖,那麼:

  • Customer

    類型的模型->

    Customer

    視圖名
  • CustomerModel

    Customer

自定義一個bootstrapper,然後添加約定到

Conventions.ViewLocationConventions

集合。

public class CustomConventionsBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
    {
        this.Conventions.ViewLocationConventions.Add((viewName, model, context) =>
        {
            return string.Concat("custom/", viewName);
        });
    }
}
      

比如這個會查找custom檔案夾下的視圖名稱。

ViewLocationConventions

是一個标準的清單,可以進行修改。

你也可以實作

IConvention

接口,并在

Initialise

方法中添加約定到

ViewLocationConventions

屬性中。

Nancy會定位所有接口的實作,并且執行約定,這些發生在他們被傳遞給bootstrapper的

ConfigureConventions

方法之前。

Nancy内建了本地化。有一系列的

約定

描述了如何決定目前文化,還有一些根據文化選擇視圖的

是以,對于

de-DE

的文化他會尋找

Home-de-DE

的視圖。

不僅如此,還會有rese檔案,比如

Text.resx

Text.de-DE.resx

(可以被

重寫

).

Razor本地化的

NuGet

來安裝

Nancy.Testing

測試應當與主應用分開。

為了測試路由,使用helper類

Browser

。使用bootstrap執行個體化Browser。

[Fact]
public void Should_return_status_ok_when_route_exists()
{
    // Given
    var bootstrapper = new DefaultNancyBootstrapper();
    var browser = new Browser(bootstrapper);

    // When
    var result = browser.Get("/", with => {
        with.HttpRequest();
    });

    // Then
    Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
      

Nancy通過

IRootPathProvider

接口的唯一方法

GetRootPath

來确定根路徑。

改變根路徑需要做兩件事:

首先,自定義一個類實作

IRootPathProvider

public class CustomRootPathProvider : IRootPathProvider
{
    public string GetRootPath()
    {
        return "What ever path you want to use as your application root";
    }
}
      

注意,根路徑是絕對路徑。

其次,在自定義的Bootstrapper中重寫

RootPathProvider

public class CustomBootstrapper : DefaultNancyBootstrapper
{
    protected override IRootPathProvider RootPathProvider
    {
        get { return new CustomRootPathProvider(); }
    }
}
      

在Nancy中要上傳檔案,你需要接受上傳檔案的content stream, 在磁盤上建立檔案,并将stream寫入到磁盤。

var uploadDirectory =  Path.Combine(pathProvider.GetRootPath(), "Content", "uploads");

if (!Directory.Exists(uploadDirectory))
{
    Directory.CreateDirectory(uploadDirectory);
}

foreach (var file in Request.Files)
{
    var filename = Path.Combine(uploadDirectory, file.Name);
    using (FileStream fileStream = new FileStream(filename, FileMode.Create))
    {
        file.Value.CopyTo(fileStream);
    }
}
      

上例中的

pathProvider

是在子產品的構造函數中傳遞進來的,通過它的

GetRootPath()

來擷取跟路徑。

public HomeModule(IRootPathProvider pathProvider)
      

簡而言之:把東西都放到

/Content

檔案夾内,僅此而已

Nancy自帶診斷功能:

http://<address-of-your-application>/_Nancy/

添加密碼:

public class CustomBootstrapper : DefaultNancyBootstrapper
{
    protected override DiagnosticsConfiguration DiagnosticsConfiguration
    {
        get { return new DiagnosticsConfiguration { Password = @"A2\6mVtH/XRT\p,B"}; }
    }
}
      

public class CustomBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(TinyIoc.TinyIoCContainer container, IPipelines pipelines)
    {
        DiagnosticsHook.Disable(pipelines);
    }
}
      

Information

Interactive Diagnostics

Request Tracing

Configuration

Nancy中

StaticConfiguration

可以用來配置程式的行為,配置頁面提供了配置方法。

注意,系統重新開機後配置頁面的内容失效。

要想永久儲存配置,請在bootstrapper的

ApplicationStartup

中設定。

請求跟蹤因為性能原因預設關閉,可以再

Configuration

頁開啟,也可以這樣:

public class CustomBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(TinyIoC.TinyIoCContainer container, IPipelines pipelines)
    {
        StaticConfiguration.EnableRequestTracing = true;
    }
}
      

跟蹤日志可以通過

NancyContext

中得到。和容易添加自己的内容:

public class HomeModule : NancyModule
{
    public HomeModule()
    {
        Get["/"] = parameters => {
            this.Context.Trace.TraceLog.WriteLog(s => s.AppendLine("Root path was called"));
            return HttpStatusCode.Ok;
        };
    }
}
      

WriteLog

方法是用一個接受

StringBuilder

的函數是為了調試關閉時直接不調用函數,進而避免性能損耗。

隻要實作了

IDiagnosticsProvider

接口,Nancy診斷會自動發現它,并且把它暴露給互動工具。

/// <summary>
/// Defines the functionality a diagnostics provider.
/// </summary>
public interface IDiagnosticsProvider
{
    /// <summary>
    /// Gets the name of the provider.
    /// </summary>
    /// <value>A <see cref="string"/> containing the name of the provider.</value>
   string Name { get; }

   /// <summary>
   /// Gets the description of the provider.
   /// </summary>
   /// <value>A <see cref="string"/> containing the description of the provider.</value>
   string Description { get; }

   /// <summary>
   /// Gets the object that contains the interactive diagnostics methods.
   /// </summary>
   /// <value>An instance of the interactive diagnostics object.</value>
   object DiagnosticObject { get; }
}
      

任何公共方法都會暴露給互動診斷面闆。方法可以是能被JSON序列化的任意類型。類型的傳回值會被傳回成

JSON Report Format

兩種方法: 1、使用attribute:

Nancy.Diagnostics.DescriptionAttribute

2、使用property:使用與方法同名但添加了

Description

字尾的屬性,比如

NameOfYourMethodDescription

描述了

NameOfYourMethod

方法。

在應用中防止一個_favicon_的檔案,名稱以

.icon

.png

在Bootstrapper中重寫

FavIcon

屬性:

public class Bootstrapper : DefaultNancyBootstrapper
{
    private byte[] favicon;

    protected override byte[] FavIcon
    {
        get { return this.favicon?? (this.favicon= LoadFavIcon()); }
    }

    private byte[] LoadFavIcon()
    {
        //TODO: remember to replace 'AssemblyName' with the prefix of the resource
        using (var resourceStream = GetType().Assembly.GetManifestResourceStream("AssemblyName.favicon.ico"))
        {
            var tempFavicon = new byte[resourceStream.Length];
            resourceStream.Read(tempFavicon, 0, (int)resourceStream.Length);
            return tempFavicon;
        }
    }
}
      

設定Bootstrapper的

FavIcon

屬性為

null

第一篇:http://mike-ward.net/blog/post/00824/custom-error-pages-in-nancyfx

第二篇:https://blog.tommyparnell.com/custom-error-pages-in-nancy/

命名空間:

Nancy.Cryptography

/// <summary>
/// Provides symmetrical encryption support
/// </summary>
public interface IEncryptionProvider
{
    /// <summary>
    /// Encrypt and base64 encode the string
    /// </summary>
    /// <param name="data">Data to encrypt</param>
    /// <returns>Encrypted string</returns>
    string Encrypt(string data);

    /// <summary>
    /// Decrypt string
    /// </summary>
    /// <param name="data">Data to decrypt</param>
    /// <returns>Decrypted string</returns>
    string Decrypt(string data);
}
      

Nancy提供了兩個預設實作

  • NoEncryptionProvider

    :沒有加密,僅僅是base64
  • RijndaelEncryptionProvider

    : 使用Rijndael算法,使用256位的key和128為的初始向量,加密base64字元串。

用來簽名,防止篡改。

/// <summary>
/// Creates Hash-based Message Authentication Codes (HMACs)
/// </summary>
public interface IHmacProvider
{
    /// <summary>
    /// Gets the length of the HMAC signature in bytes
    /// </summary>
    int HmacLength { get; }

    /// <summary>
    /// Create a hmac from the given data
    /// </summary>
    /// <param name="data">Data to create hmac from</param>
    /// <returns>Hmac bytes</returns>
    byte[] GenerateHmac(string data);

    /// <summary>
    /// Create a hmac from the given data
    /// </summary>
    /// <param name="data">Data to create hmac from</param>
    /// <returns>Hmac bytes</returns>
    byte[] GenerateHmac(byte[] data);
}
      

Nancy也提供了一個預設實作:

DefaultHmacProvider

,使用

IKeyGenerator

來産生一個key來用SHA-256來進行hash。

用來産生key來加密和數字簽名。

/// <summary>
/// Provides key byte generation
/// </summary>
public interface IKeyGenerator
{
    /// <summary>
    /// Generate a sequence of bytes
    /// </summary>
    /// <param name="count">Number of bytes to return</param>
    /// <returns>Array <see cref="count"/> bytes</returns>
    byte[] GetBytes(int count);
}
      

Nancy提供了兩個預設實作。

  • RandomKeyGenerator

    RNGCryptoServiceProvider

    産生了一個随機定長的key
  • PassphraseKeyGenerator

    使用密碼、靜态鹽以及可選循環數字,以及

    Rfc2898DeriveBytes

    來産生一個key

注意,如果使用

PassphraseKeyGenerator

,它的初始化應當在應用啟動時使用,因為它太慢了。這意味着鹽是靜态的,是以密碼一定要足夠長和複雜。

這是一個存儲

IEncryptionProvider

IHmacProvider

的簡便方法。它有兩個靜态屬性:

  • Default

    RijndaelEncryptionProvider

    DefaultHmacProvider

    ,兩個都使用

    RandomKeyGenerator

  • NoEncryption

    NoEncryption

    DefaultHmacProvider

    ,兩個也都使用

    RandomKeyGenerator

可以單獨使用

CryptographyConfiguration

,也可以在bootstrapper中配置一個:

/// <summary>
/// Gets the cryptography configuration
/// </summary>
protected virtual CryptographyConfiguration CryptographyConfiguration
{
    get { return CryptographyConfiguration.Default; }
}
      

當傳回不是

Response

類型時,使用response processor來根據請求的

Accept

來處理。

public interface IResponseProcessor
{
    /// <summary>
    /// Gets a set of mappings that map a given extension (such as .json)
    /// to a media range that can be sent to the client in a vary header.
    /// </summary>
    IEnumerable<Tuple<string, MediaRange>> ExtensionMappings { get; }

    /// <summary>
    /// Determines whether the the processor can handle a given content type and model.
    /// </summary>
    ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context);

    /// <summary>
    /// Process the response.
    /// </summary>
    Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context);
}
      

Response Processor是自發現的,也可以在Bootstrap中配置。

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override NancyInternalConfiguration InternalConfiguration
    {
        get
        {
            var processors = new[]
            {
                typeof(SomeProcessor),
                typeof(AnotherProcessor)
            };

            return NancyInternalConfiguration.WithOverrides(x => x.ResponseProcessors = processors);
        }
    }
}
      

當相應準備轉化請求媒體的格式時,Nancy會查詢所有的processor的

CanProcess

方法,并且會聚合

ProcessorMatch

的傳回值。

ProcessorMatch

類型確定每個processor讓Nancy知道它們對媒體類型的支援程度。

public class ProcessorMatch
{
    /// <summary>
    /// Gets or sets the match result based on the content type
    /// </summary>
    public MatchResult RequestedContentTypeResult { get; set; }

    /// <summary>
    /// Gets or sets the match result based on the model
    /// </summary>
    public MatchResult ModelResult { get; set; }
}
      

MatchResult

枚舉了比對程度:

public enum MatchResult
{
    /// <summary>
    /// No match, nothing to see here, move along
    /// </summary>
    NoMatch,

    /// <summary>
    /// Will accept anything
    /// </summary>
    DontCare,

    /// <summary>
    /// Matched, but in a non-specific way such as a wildcard match or fallback
    /// </summary>
    NonExactMatch,

    /// <summary>
    /// Exact specific match
    /// </summary>
    ExactMatch
}
      

所有的

ProcessorMatch

會按照Match程度降序排列,最比對的被執行。如果有兩個比對程度相同,Nancy會選擇其中一個。

Nancy提供了一些預設響應處理器

  • JsonProcessor

    - 當請求類型為

    application/json

    application/vnd.foobar+json

    時,轉化傳回值為json;
  • ViewProcessor

    text/html

    時,使用傳回值作為model,傳回視圖。視圖使用
  • XmlProcessor

    - 當請求為

    application/xml

    或者為

    application/vnd.foobar+xml

    時,傳回xml。

Nancy.Responses.Negotiation

命名空間中的

Negotiator

用來控制協商。

Negotiator

有一個屬性:

NegotiationContext

NegotiationContext

可以用來控制響應的協商。

但是一般不會直接使用

Negotiator

NegotiationContext

,因為

NancyModule

包含了一個幫助方法

Negotiate

,用來更好的創造

Negotiator

執行個體。

在路由中使用

Negotiator

的例子:

Get["/"] = parameters => {
    return Negotiate
        .WithModel(new RatPack {FirstName = "Nancy "})
        .WithMediaRangeModel("text/html", new RatPack {FirstName = "Nancy fancy pants"})
        .WithView("negotiatedview")
        .WithHeader("X-Custom", "SomeValue");
};
      

Negotiator

包含了用來配置傳回

Negotiator

執行個體的一些方法。

  • WithHeader

    - 添加一個Http頭;
  • WithHeaders

    - 添加一個Http的頭集合;
  • WithView

    - 使用視圖;
  • WithModel

    - 使用模型;
  • WithMediaRangeModel

    - 使用特定的媒體類型和模型,如果失敗了,就使用

    WithModel

    指定的模型;
  • WithFullNegotiation

    - 設定允許媒體類型為

    */*

    的幫助方法;
  • WithAllowedMediaRange

    - 指定允許的媒體範圍。預設是"/",但是一旦指定一個特定的内容類型,通配符就會被移走。
  • WithStatusCode

    - 狀态碼

Nancy支援基于擴充名來設定協商的處理,此時傳遞正常的可接受的頭。

Get["/ratpack"] = parameters => {
    return new RatPack {FirstName = "Nancy "});
};
      

它既可以通過

/ratpack

和設定的

application/json

頭來調用,也可以使用

/ratpack.json

并且不設定

application/json

來調用,兩個結果一樣。

内部Nancy是通過檢測擴充名,并查詢可用的響應處理器的

ExtensionMappings

屬性來檢視是否有支援的擴充。如果有,就調用并且設定對應的頭資訊,但是如果有更優先的處理器,則用更優先的處理器,除非更優先的處理器失敗了,才會使用擴充。

約定的格式:

Func<
   IEnumerable<Tuple<string, decimal>>,
   NancyContext,
   IEnumerable<Tuple<string, decimal>>>
      

這個函數接受

NancyContext

和目前頭,并且期望你傳回修改後的可接受頭清單。

預設情況下,Nancy在

Nancy.Conventions.BuiltInAcceptHeaderCoercions class

中提供了如下約定,其中加*的表示是預設預設被轉換的:

  • BoostHtml

    (*) - 如果text/html的優先級低于其他内容類型,則提高優先級;
  • CoerceBlankAcceptHeader

    (*) - 如果沒有指定請求頭,就配置設定一個預設的;
  • CoerceStupidBrowsers

    - 對于老浏覽器,替換請求頭,即使它們說是請求xml還是傳回html。

更改哪一個強制起作用時在bootstrapper中的

ConfigureConventions

來設定的:

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override void ConfigureConventions(NancyConventions nancyConventions)
    {
        base.ConfigureConventions(nancyConventions);

        this.Conventions.AcceptHeaderCoercionConventions.Add((acceptHeaders, ctx) => {

            // Modify the acceptHeaders by adding, removing or updating the current
            // values.

            return acceptHeaders;
        });
    }
}
      

當然你也可以繼承你自己的bootstrapper。

可以通過實作

IConventions

接口來創造一個類,并在它的

Initialise

方法中添加自己的約定到傳遞進來的參數的

AcceptHeaderCoercionConventions

在所有的接口被傳遞給bootstrapper的

ConfigureConventions

的方法之前,Nancy會定位所有的接口實作,并且激發這些約定。

Nancy會自動添加連結和各種各樣的頭到協商響應中。連結頭連結。連接配接頭會連接配接到根據檔案擴充來的其他代表中。

Nancy中的驗證使用擴充點:比如應用管道、子產品管道、

NancyContext

和其他的一些擴充方法。是以你可以寫自己的驗證來替換預設提供的驗證。

Nancy提供了以下幾種驗證,通過Nuget安裝:

  • 表單(

    Nancy.Authentication.Forms

  • 基本(

    Nancy.Authentication.Basic

  • 無狀态(

    Nancy.Authentication.Stateless

Nancy中使用者使用

IUserIdentity

接口代表,它提供了一些使用者的基本資訊:

public interface IUserIdentity
{
    /// <summary>
    /// Gets or sets the name of the current user.
    /// </summary>
    string UserName { get; set; }

    /// <summary>
    /// Gets or set the claims of the current user.
    /// </summary>
    IEnumerable<string> Claims { get; set; } 
}
      

你應當提供基于自己應用需求的類來實作自己的使用者接口。

要獲得目前使用者,隻需要擷取

NancyContext

CurrentUser

屬性。傳回

null

值表明目前請求未認證,其他的則表示已認證。

context在Nancy的大部分地方都能擷取,是以不必擔心能否擷取目前請求的使用者身份。

可以在子產品級和應用級來保護資源,方法是檢測

NancyContext.CurrentUser

屬性不為null。

這個任務可以通過在

子產品管道

Before

中實作。這個鈎子允許我們終結目前請求的執行,傳回其它資源,比如當未驗證使用者視圖通路安全資源時:

public class SecureModule : NancyModule
{
    public SecureModule()
    {
        Before += ctx => {
            return (this.Context.CurrentUser == null) ? new HtmlResponse(HttpStatusCode.Unauthorized) : null;
        };

        // Your routes here
    }
}
      

在每個子產品上添加安全代碼違反了DRY原則,更是一個無聊的任務。使用擴充方法!

Nancy有一些擴充方法包裝了這些任務,徹底的減少了要寫的代碼量。

下面是一些可用的擴充方法:

  • RequiresAuthentication

    - 確定驗證使用者是可用的,或者傳回

    HttpStatusCode.Unauthorized

    . 對于認證的使用者,

    CurrentUser

    不能為

    null

    ,而且

    UserName

    不能為空;
  • RequiresClaims

    - 使用者必須滿足聲明清單中所有的條件才能擷取資源;
  • RequiresAnyClaim

    - 見上一條,但是隻需滿足任意一條;
  • RequiresValidatedClaims

    - 通過自定義函數,來全部自我掌控驗證流程,函數格式

    Func<IEnumerable<string>, bool>

  • RequiresHttps

    - 隻允許https通路;

這些都是

NancyModule

類的擴充方法,要使用它們需要添加

Nancy.Security

命名空間。

使用擴充方法,前面的例子可以這樣寫:

public class SecureModule : NancyModule
{
    public SecureModule()
    {
        this.RequiresAuthentication();
    }

    // Your routes here
}
      

當然還可以這樣寫:

public class SecureModule : NancyModule
{
    public SecureModule()
    {
        this.RequiresHttps();
        this.RequiresAuthentication();
        this.RequiresClaims(new [] { "Admin" });
    }

    // Your routes here
}
      

使用者必須通過https,被授權,而且擁有Admin claim才能通路上面的路由。

為了創造自己的安全擴充,你隻需要添加擴充方法到

NancyModule

,并且綁定到

Before

管道,并檢查證書。

比如,下面說明了

RequiresAuthentication

如何工作的:

public static class ModuleSecurity
{
    public static void RequiresAuthentication(this NancyModule module)
    {
        module.Before.AddItemToEndOfPipeline(RequiresAuthentication);
    }

    private static Response RequiresAuthentication(NancyContext context)
    {
        Response response = null;
        if ((context.CurrentUser == null) ||
            String.IsNullOrWhiteSpace(context.CurrentUser.UserName))
        {
            response = new Response { StatusCode = HttpStatusCode.Unauthorized };
        }

        return response;
    }

}
      

實際的驗證provider實作根據不同的需求變化很大,但是基本模式如下:

  1. 應用管道

    Before

    鈎子用來檢查請求的證書(比如cookie, headers等等)。如果發現證書,則驗證使用者并授權給

    NancyContext

    CurrentUser

  2. Before

    鈎子用來确認目前的請求是被認證的使用者執行,如果不是,則拒絕并傳回

    HttpStatusCode.Unauthorized

  3. After

    鈎子用來檢查請求是否因為認證失敗而被丢棄,比如檢查

    HttpStatusCode.Unauthorized

    (401)狀态碼。如果檢測到了就幫助使用者去認證,比如重定向到login表單或者使用header的幫助通知用戶端。

無狀态認證就是在每個請求中進行檢查,根據請求的一些資訊,來決定是否應該被确認為一個已認證的請求。

比如你檢查請求來确認查詢字元串的參數是否傳遞了api key,或者是否包含某些head, 有或者請求是否來自某些特定的ip。

使用無狀态認證需要做下面幾件事: 1. 安裝

Nancy.Authentication.Stateless

包 2. 配置并開啟無狀态認證 3.

保護資源

在bootstrapper中添加:

StatelessAuthentication.Enable(pipelines, statelessAuthConfiguration);
      

被傳遞到

StatelessAuthentication.Enable

方法中的

statelessAuthConfiguration

變量,是一個

StatelessAuthenticationConfiguration

類型的執行個體,它能夠讓你自定義無狀态認證提供者的行為。

定義

StatelessAuthenticationConfiguration

類型執行個體的時候,需要有一個

Func<NancyContext, IUserIdentity>

類型的參數。這個函數用來檢查請求或者context中的其他相關内容,并且在請求未通過驗證時傳回

null

,否則傳回合适的

IUserIdentity

var configuration =
    new StatelessAuthenticationConfiguration(ctx =>
    {
        if (!ctx.Request.Query.apikey.HasValue)
        {
            return null;
        }

        // This would where you authenticated the request. IUserApiMapper is
        // not a Nancy type.
        var userValidator = 
            container.Resolve<IUserApiMapper>();

        return userValidator.GetUserFromAccessToken(ctx.Request.Query.apikey);
    });
      

詳細例子見Nancy解決方案中

Nancy.Demo.Authentication.Forms

為了開啟form認證,需要完成:

  1. Nancy.Authentication.Forms

  2. IUserMapper

  3. 實作路由來處理login和logout
  4. 配置并開啟Form認證

User mapper用來負責從标示符identifier映射到使用者。标示符是一個令牌,被存儲在認證cookie中,用來代表執行請求的使用者身份,避免每次請求時輸入證書。

使用GUID來做标示符,如果用username來做标示符容易被嗅探并攻擊。GUID還很難讀取,而且每個GUID都不一樣,增加了嗅探的難度。

注意,需要知道标示符對每個使用者來說都是永久的并且是唯一的。

IUserMapper

接口的定義:

public interface IUserMapper
{
    /// <summary>
    /// Get the real username from an identifier
    /// </summary>
    /// <param name="identifier">User identifier</param>
    /// <param name="context">The current NancyFx context</param>
    /// <returns>Matching populated IUserIdentity object, or empty</returns>
    IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context);
}
      

有了

IUserMapper

後,下一步就是在不需要認證的地方添加login和logout了。

下面是一個子產品的基礎架構。請注意資源的路徑和子產品的名稱可以使任意的:

public class LoginModule : NancyModule
{
    public LoginModule()
    {
        Get["/login"] = parameters => {
            // Called when the user visits the login page or is redirected here because
            // an attempt was made to access a restricted resource. It should return
            // the view that contains the login form
        };

        Get["/logout"] = parameters => {
            // Called when the user clicks the sign out button in the application. Should
            // perform one of the Logout actions (see below)
        };

        Post["/login"] = parameters => {
            // Called when the user submits the contents of the login form. Should
            // validate the user based on the posted form data, and perform one of the
            // Login actions (see below)
        };
    }
}
      

Nancy.Authentication.Forms

命名空間中有一些擴充方法可供使用:

  • LoginAndRedirect

    - 登入使用者并重定向使用者到他們來時的url。或者也可以提供一個預留的url,用來在沒有重定向url時使用。如果使用form送出,注意使用action="",因為它會保留returnUrl原封不動。
  • LoginWithoutRedirect

    - 登入使用者,并且傳回響應和狀态碼200(ok)
  • Login

    會調用目前請求的

    IsAjaxRequest

    的擴充方法,并且如果不是Ajax調用,則執行

    LoginAndRedirect

    方法,否則執行

    LoginWithoutRedirect

    方法
  • LogoutAndRedirect

    - 登出使用者,并提供重定向
  • LogoutWithoutRedirect

    - 登出使用者并傳回狀态碼為200(OK)的響應
  • Logout

    IsAjaxRequest

    方法,如果不是ajax請求,則執行

    LogoutAndRedirect

    ,否則執行

    LogoutWithoutRedirect

注意1:

Nancy.Extensions.RequestExtensions

中的

IsAjaxRequest

擴充方法會檢查

X-Requested-With

頭,并且在其包含值

XMLHttpRequest

時傳回true

注意2: 請确認路徑的定義login和logout的頁面沒有要求使用登入。

FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
      

既可以在

ApplicationStartup

中又可以在

RequestStartup

中添加。到底在何處加,取決于

IUserMapper

,即user mapper到底是有應用級的生命周期還是請求級的生命周期。

傳遞給

FormsAuthentication.Enable

方法的

formsAuthConfiguration

變量是

FormsAuthenticationConfiguration

類型,它能讓你自定義form認證提供者的行為。

比如,下面是一個基本的認證配置:

var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
    RedirectUrl = "~/login",
    UserMapper = container.Resolve<IUserMapper>(),
};
      

下面是一些配置項:

  • RedirectingQuerystringKey

    :預設名是

    returnUrl

  • RedirectingUrl

    :未認證的使用者應當被重定向的url,一般是登入頁面

    ~/login

  • UserMapper

    :

    IUserMapper

    在認證時應該被使用
  • RequiresSSL

    : SSL
  • DisableRedirect

    : 遇到未認證時,是否重定向到登陸頁
  • CryptographyConfiguration

    CryptographyConfiguration.Default

    與form認證cookie配合使用。

    CryptographyConfiguration.Default

    是預設的。

預設使用

RandomKeyGenerator

,這意味着每次程式啟動時會産生一個新的秘鑰,那麼應用重新開機回到這認證cookie失效,在多台機器負載均衡時也會出現這種問題,别怕,看看

加密配置

下面是一個例子:

var cryptographyConfiguration = new CryptographyConfiguration(
    new RijndaelEncryptionProvider(new PassphraseKeyGenerator("SuperSecretPass", new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })),
    new DefaultHmacProvider(new PassphraseKeyGenerator("UberSuperSecure", new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })));

var config = 
    new FormsAuthenticationConfiguration()
    {
        CryptographyConfiguration = cryptographyConfiguration,
        RedirectUrl = "/login",
        UserMapper = container.Resolve<IUserMapper>(),
    };
      

詳細例子在Nancy解決方案中的

Nancy.Demo.Authentication.Token

中。

Nancy令牌認證工程是為了多種用戶端(iOS, Android, Angular SPA等等)能與統一背景Nancy應用而建立的。

令牌認證與授權在下面這些需求下應運而生:

  • 沒有cookie(不适所有的用戶端都是浏覽器)
  • 避免一旦使用者被認證/授權後,從後端資料存儲中取回使用者和權限資訊
  • 允許用戶端應用在第一次授權後儲存令牌,以便為後續請求使用
  • 通過單向加密算法確定令牌沒有被篡改,阻止嗅探冒充令牌攻擊
  • 使用有期限的可配置的key來進行令牌生成
  • 使用server端的檔案系統來存儲私鑰,這樣即使應用重新開機也能恢複。注意:可以使用記憶體存儲作為測試。

令牌認證可以像form認證那樣:

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
    {
        TokenAuthentication.Enable(pipelines, new TokenAuthenticationConfiguration(container.Resolve<ITokenizer>()));
    }
}
      

令牌從

IUserIdentity

NancyContext

中,通過實作

ITokenizer

接口産生。預設實作是

Tokenizer

,它提供了一些可配置的方法。預設情況下,它産生一個令牌包含下面部分:

  • 使用者名
  • Pipe separated list of user claims
  • UTC目前時間
  • 用戶端的"User-Agent"頭(必須)

建議配置Tokenizer,使用其他附加能代表使用者唯一裝置的資訊。

下面舉例說明了如何初始化使用者認證,并且傳回生成的令牌給用戶端:

public class AuthModule : NancyModule
{
    public AuthModule(ITokenizer tokenizer)
        : base("/auth")
    {
        Post["/"] = x =>
            {
                var userName = (string)this.Request.Form.UserName;
                var password = (string)this.Request.Form.Password;

                var userIdentity = UserDatabase.ValidateUser(userName, password);

                if (userIdentity == null)
                {
                    return HttpStatusCode.Unauthorized;
                }

                var token = tokenizer.Tokenize(userIdentity, Context);

                return new
                    {
                        Token = token,
                    };
            };

        Get["/validation"] = _ =>
            {
                this.RequiresAuthentication();
                return "Yay! You are authenticated!";
            };

        Get["/admin"] = _ =>
        {
            this.RequiresClaims(new[] { "admin" });
            return "Yay! You are authorized!";
        };
    }
}
      

一旦你的用戶端接收到了token,那麼你必須使用token來設定HTTP頭:

Authorization: Token {your-token-goes-here}
      

https://github.com/NancyFx/Nancy/commit/9ae0a5494bc335c3d940d730ae5d5f18c1018836