1. 程式人生 > >Host ASP.NET WebApi in Owin

Host ASP.NET WebApi in Owin

public define nuget get log 文檔 getname hang null

什麽是OWIN                                          

  Owin其實是微軟為了解耦.Net Web app對IIS的依賴而制定的一套規範,規範定義了Web Server與Web App之間的接口,這樣Web App就可以Host在所有兼容OWIN規範的Web Server了(包含控制臺應用和Windows服務...)。具體來說,Owin將Web app和Web Server整體劃分為以下幾個模塊。

技術分享

Host

根據官方解釋,Host是指server和application所依托執行的進程。主要負責應用的啟動與配置。

Server

Server負責直接與客戶端進行Http通信,然後將請求轉換為Owin的語義,然後用Owin的流程進行處理的Http Server.

Middleware

Middleware是開發人員自行註冊到Owin處理請求管道中的模塊(類似IIS中的Module),可以直接參與請求的處理。所以我認為Web Api框架其實也屬於Middleware.(不過官方將Web Api框架歸屬於Web Framework。它會將Owin的語義轉換為Web Framework內部的語義,然後按照內部的處理流程處理請求)

Application

Application就是基於所有上面所有Middleware(準確說應該是Web Framework,看來官方定義一個Web Framework模塊是有意義的)所構建的應用層。

這些定義只是一些描述,那具體實施是怎麽實施呢。微軟自己有一個開源項目叫Katana,它是對Owin規範的官方實現(其實主要就是實現上述的Host/Server部分,因為Middleware,Application部分都是需要我們自己開發的)。下面通過使用Katana,我們將web api部署在一個控制臺進程中,來看看具體怎麽去使用它以及OWIN接口到底是什麽。

Host WebApi in Console App                                  

1.首先我們創建一個控制臺應用

2.然後我們通過Nuget引入Package Microsoft.AspNet.WebApi.OwinSelfHost,安裝過程中所有依賴的Owin和Web Api的package都會一並安裝。

3.我們可以添加一個ApiController:PersonController,並添加一個接口方法:

 1 [RoutePrefix("api/persons")]
 2     public class PersonController : ApiController
 3     {
 4         [Route("{id}/name")] 6         public string getName(string id)
 7         {
 8             return id + "@boss";
 9         }
10     }

4. 接下來我們需要將web api進行配置和部署。該Katana登場了。

首先按照Owin約定我們得添加一個用於Startup的類,這個類中需要有一個簽名為Configuration(IAppBuilder app)的方法:

public class Startup
{
        public void Configuration(IAppBuilder appBuilder)
        {var configuration = new HttpConfiguration();
            configuration.MapHttpAttributeRoutes();//配置web api的router
            appBuilder.UseWebApi(configuration);//這個擴展是由package Microsoft.AspNet.WebApi.Owin提供,它負責註冊web api到owin的處理管道中,
                            //並在處理請求時將Owin語義與web api中的語義進行轉換 } }

我們在Main函數中添加如下代碼:

WebApp.Start<Startup>("http://localhost:8088/");//WebApp利用katana實現的OwinHttpListener來在指定url上監聽http請求。

Console.WriteLine("Started!");
Console.ReadKey();

這樣我們就基於katana實現了在console app中運行web api了。

F5運行以下,看看效果。

然後可以通過瀏覽器訪問http://localhost:8088/api/persons/123/name,應該能看到如下畫面

技術分享

Add Authenticate Middleware                           

接下來我們通過添加owin middleware的方式來為web api添加保護機制(Authentication)。在這之前我先解釋下關於Middleware的基礎知識。

Middleware是一組Owin server在處理http請求的時候會輪流調用到的模塊,他們通過調用IAppBuilder的Use擴展方法來註冊。運行時的Middleware的調用順序與註冊順序一致。

並且對管道中下一Middleware的調用是由當前執行的Middleware來執行。具體到接口來說是這樣:

1. OWIN定義了一個Middleware的執行接口Func<IDictionary<string,object>,Task>,然後要求每個Middleware的定義需滿足如下條件:

  • 提供接受一個類型為執行接口類型(Func<IDictionary<string,object>,Task>)的構造函數
  •  提供一個滿足如下簽名的方法Task Invoke(IDictionary<string,object> parameters)

也就是說,owin將每個middleware最後都抽象成了一個函數,這個函數接受IDictionary作為參數,返回一個執行具體處理的Task.

2. OWIN在創建每個Middleware的實例時,會根據註冊順序傳入當前Middleware在執行管道中的下一個Middleware的執行接口。Middleware需要存儲起來後續調用。

3. 當處理請求時,Server會調用管道中第一個Middleware的Invoke方法,然後由該Middleware決定處理完請求後是否調用下一個Middleware. 在調用Invoke時,Owin server

會將當前請求的所有上下文屬性傳入該Dictionary對象中。詳細的上下文屬性列表見官方文檔。

我們在當前項目中新建AuthenticateMiddleWare,代碼如下:

public class AuthenticateMiddleware
{
        private Func<IDictionary<string, object>, Task> nextAppFunc;
        public AuthenticateMiddleware(Func<IDictionary<string, object>, Task> nextMiddleWareFunc)
        {
            nextAppFunc = nextMiddleWareFunc;
        }

        public async Task Invoke(IDictionary<string, object> parameters)
        {
            Console.WriteLine("Authenticating");
            string queryString = parameters["owin.RequestQueryString"] as string;//獲取http請求的query string
            var respStream = parameters["owin.ResponseBody"] as Stream;//獲取http請求的response stream
            var streamWriter = new StreamWriter(respStream);
            var queryDic = ParseQueryString(queryString);

            const string tokenKey = "token";
            const string predefineToken = "88888888";
            if (!queryDic.ContainsKey(tokenKey)||queryDic[tokenKey]!=predefineToken)//檢查請求中所帶token是否合法,此處僅為測試需要,直接硬編碼。
            {
         //如果token非法,則直接寫入Access Denied到response中。停止繼續執行管道中其他middleware. streamWriter.WriteLine(
"Access Denied!"); streamWriter.Flush(); return; }
      
var identity = new GenericIdentity("boss zhang"); parameters["server.User"] = new GenericPrincipal(identity, new string[] { "admin" });//token合法,生成principal對象到parameters中,key "server.User"
                                                        //用於存儲當前請求的user信息,相當於HttpContext.User
if (nextAppFunc != null) { await nextAppFunc.Invoke(parameters);//繼續執行管道中下一個middleware } } private Dictionary<string, string> ParseQueryString(string originalString) { string[] queryStringItems = originalString.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries); var queryStringDic = new Dictionary<string, string>(); foreach (var item in queryStringItems) { string[] queryStringKvp = item.Split(new string[] { "=" }, StringSplitOptions.None); if (queryStringKvp.Length == 2) { queryStringDic[queryStringKvp[0]] = queryStringKvp[1]; } } return queryStringDic; }
}

然後再給之前定義的PersonController加上Authorize Attribute加以保護。

F5運行,然後在瀏覽器中訪問以下url: http://localhost:8088/api/persons/123/name?token=88888888.依然能得到之前正確的返回。

如果去掉token=88888888,則得到如下結果。

技術分享

這說明我們的AuthenticateMiddle發揮作用了。

完整代碼見https://github.com/lbwxly/OwinSample.git

參考文檔:

http://www.dotnetcurry.com/signalr/915/owin-katana-signalr-web-server

https://ovaismehboob.com/2014/12/01/understanding-owin-by-developing-a-custom-owin-middleware-component/

Host ASP.NET WebApi in Owin