https://msdn.microsoft.com/zh-cn/magazine/dn451439.aspx (Katana 項目入門)
OWIN知識
OWIN的全稱是Open Web Interface For .Net。
OWIN提供的只是一種規范,而沒有具體實現。其目的是在web服務器和應用程序組件之間隔離出一個抽象層,使它們之間解耦。
應用程序委托和環境字典
OWIN將服務器與應用程序之間的交互減少到一小部分類型和單個函數簽名,這個函數簽名被稱為應用程序委托(即 AppFunc):
using AppFunc = Func<IDictionary<string, object>, Task>;
基於 OWIN 的應用程序中的每個組件都向服務器提供應用程序委托。 然后,這些組件鏈接成一個管道,基於 OWIN 的服務器將會向該管道推送請求。
一個符合OWIN的web服務器,需要將請求信息(應用程序狀態、請求狀態和服務器狀態等所有相關信息)包裝到一個字典里(應用程序委托上指定的 IDictionary<string, object>,這種數據結構稱為環境字典),從而使得許多不同的框架和組件作者可以在一個 OWIN 管道中進行互操作,而不必強制實施對特定 .NET 對象模型的協議。
雖然任何鍵/值數據都可以插入到環境字典中,但 OWIN 規范為某些 HTTP 核心元素定義了鍵:
key | value |
"owin.RequestBody" | 一個帶有請求正文(如果有)的流。如果沒有請求正文,Stream.Null 可以用作占位符。 |
"owin.RequestHeaders" | 請求標頭的 IDictionary<string, string[]>。 |
"owin.RequestMethod" | 一個包含請求的 HTTP 請求方法的字符串(例如 GET 和 POST)。 |
"owin.RequestPath" | 一個包含請求路徑的字符串。 此路徑必須是應用程序委托的“根”的相對路徑。 |
"owin.RequestPathBase" | 一個字符串,包含對應於應用程序委托的“根”的請求路徑部分。 |
"owin.RequestProtocol" | 一個包含協議名稱和版本的字符串(例如 HTTP/1.0 或 HTTP/1.1)。 |
"owin.RequestQueryString" | 一個字符串,包含 HTTP 請求 URI 的查詢字符串組成部分,不帶前導“?”(例如 foo=bar&baz=quux)。 該值可以是空字符串。 |
"owin.RequestScheme" | 一個字符串,包含用於請求的 URI 方案(例如 HTTP 或 HTTPS)。 |
隨着請求在OWIN管道中流動,每個中間件(Middleware,集成到管道中的組件或應用程序)所要做的就是讀取、修改這個字典的數據。最后,Web服務器得到這個層層處理過的字典,然后輸出網頁到客戶端。
與ASP.NET管道相比,OWIN規范非常簡潔,且並沒有引用.Net Framework中的System.Web.dll。
1. 新的組件能夠非常簡單的開發和應用
2. 程序能夠簡便地在host和OS上遷移
Katana
Katana 項目是 Microsoft 創建和推出的基於 OWIN 的組件和框架集合。
https://katanaproject.codeplex.com
但上面這個項目最后一次提交是15年1月
目前它的托管代碼已經被拆分:
New 'Katana' Source:
- https://github.com/aspnet/HttpAbstractions
- https://github.com/aspnet/Hosting
- https://github.com/aspnet/Security
- https://github.com/aspnet/StaticFiles
- ...
Katana 體系結構
- 主機:運行應用程序的進程,可以是從 IIS 或獨立可執行文件到您自己的自定義程序的任何內容。 主機負責啟動、加載其他 OWIN 組件和正常關閉。
- 服務器:負責綁定到 TCP 端口,構造環境字典和通過 OWIN 管道處理請求。
- 中間件:這是為處理 OWIN 管道中的請求的所有組件指定的名稱。 它可以是從簡單壓縮組件到 ASP.NET Web API 這樣的完整框架,不過從服務器的角度而言,它只是一個公開應用程序委托的組件。
- 應用程序:這是您的代碼。 由於 Katana 並不取代 ASP.NET,而是一種編寫和托管組件的新方式,因此現有的 ASP.NET Web API 和 SignalR 應用程序將保持不變,因為這些框架可以參與 OWIN 管道。 事實上,對於這些類型的應用程序,Katana 組件只需使用一個小的配置類即可見。
在體系結構方面,Katana 可以按其中每個層都能夠輕松替代的方式分解,通常不需要重新生成代碼。 在處理 HTTP 請求時,各個層一起工作,方式類似於下圖中所示的數據流。
————
使用IIS做Host和Server
首先建立一個空的web應用程序。因為默認的 Katana 主機會在此 /bin 文件夾中查找程序集。而Web應用程序默認會將編譯的程序集直接放在 /bin 文件夾而不是 /bin/debug 文件夾中。
刪除無關文件,引入OWIN的支持包
添加Startup啟動類
Startup類的作用是用來初始化OWIN管道,這里,我們添加和初始化OWIN管道中的Middleware.
在Startup.Configuration方法中,添加如下代碼:
public class Startup { public void Configuration(IAppBuilder app) { // New code: app.Run(context => { context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("Hello, world."+ context.Request.Uri); }); } }
上面的代碼做的事情,就是把一個簡單的Middleware注冊到OWIN管道中。
其中context的類型是IOwinContext:
public interface IOwinContext { // Gets the Authentication middleware functionality available on the current request. IAuthenticationManager Authentication { get; } // Gets the OWIN environment. IDictionary<string, object> Environment { get; } // Gets a wrapper exposing request specific properties. IOwinRequest Request { get; } // Gets a wrapper exposing response specific properties. IOwinResponse Response { get; } // Gets or sets the host.TraceOutput environment value. TextWriter TraceOutput { get; set; } // Gets a value from the OWIN environment, or returns default(T) if not present. T Get<T>(string key); // Sets the given key and value in the OWIN environment. IOwinContext Set<T>(string key, T value); }
運行結果:
如圖 可以順利解析到不同的訪問Url,自然也就可以在后續的處理中做出不同的處理,直接分支處理或讀取靜態文件或者實現MVC架構等等……。
改用其他形式的Host和Server
上例中IIS同時充當了Host和Server的角色,
首先創建一個簡單的Console應用程序,用Nuget添加Microsoft.Owin.SelfHost
以同樣的方式添加Startup啟動類
將控制台程序改造為Host
class Program { static void Main(string[] args) { using (Microsoft.Owin.Hosting.WebApp.Start<Startup1>("http://localhost:9000")) { Console.WriteLine("Press [enter] to quit..."); Console.ReadLine(); } } }
運行結果:
Startup類
無論使用IIS, IIS Express還是OWIN Host, 微軟在這些Host上實現的Service都會依照特定的規則來尋找到Startup類,執行Configuration方法,注冊Middleware。
默認名稱匹配
可以定義Startup.cs類,只要這個類的namespace和Assembly的名稱相同。那么,這個Startup.cs中的Configuration方法,就會在OWIN管道初始化的時候執行。
使用OwinStartup Attribute
直接指定哪個具體類是Startup類。
在配置文件的appSetting 節點設置
<appSettings> <add key="owin:appStartup" value="StartupDemo.ProductionStartup" /> </appSettings>
路由配置
protected void Application_Start(object sender, EventArgs e) { // Registers a route for the default OWIN application. RouteTable.Routes.MapOwinPath("/owin"); // Invokes the System.Action startup delegate to build the OWIN application and // then registers a route for it on the given path. RouteTable.Routes.MapOwinPath("/special", app => { app.Run(OwinApp2.Invoke); }); }
public class OwinApp2 { // Invoked once per request. public static Task Invoke(IOwinContext context) { context.Response.ContentType = "text/plain"; return context.Response.WriteAsync("Hello World 2"); } }
自定義Middleware
通過繼承OwinMiddleware基類可以便捷地新建中間件:
public class HelloWorldMiddleware : OwinMiddleware { public HelloWorldMiddleware(OwinMiddleware next) : base(next) { } public override Task Invoke(IOwinContext context) { var response = "Hello World! It is " + DateTime.Now; context.Response.Write(response); return Next.Invoke(context); } }
注冊:
public class Startup { public void Configuration(IAppBuilder app) { app.Use<HelloWorldMiddleware>(); } }
應用
ASP.NET Web Form,ASP.NET MVC5項目結合OWIN
由於ASP.NET Web Form和ASP.NET MVC5依賴於System.Web.dll中的很多類型,而在OWIN管道中,是無法提供這些依賴的。所以ASP.NET Web Form和ASP.NET MVC5不能作為一個中間件直接集成到OWIN管道中。
但在這些項目中,也可以添加Startup.cs, 指定成為OWIN的初始化類型,那么請求會先經過OWIN管道處理,最后轉向ASP.NET Web Form或者ASP.NET MVC程序。這種方式,常常用來配置log, authentication, cache等等這些Middleware。
引入OWIN后的管道執行順序
Web API作為Middleware注冊到OWIN管道中
Web API由於無任何依賴於System.web.dll, 所以可以作為Middleware注冊到OWIN管道中。
public class Startup { // Invoked once at startup to configure your application. public void Configuration(IAppBuilder builder) { HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttpRoute("Default", "api/{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional });//定義web api route //xml格式輸出結果 config.Formatters.XmlFormatter.UseXmlSerializer = true; config.Formatters.Remove(config.Formatters.JsonFormatter); // config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true; //將web api以Middleware注冊到OWIN管道中 builder.UseWebApi(config); } }
ASP.NET 5
ASP.NET 5中終於去System.web.dll化,MVC,Web API都統一在了OWIN管道中。
ASP.NET 5 的 project.json 配置文件(Microsoft.Owin改做Microsoft.AspNet 了):
ASP.NET 5 中Startup.cs 的兩個重要方法:
// This method gets called by the runtime. public void ConfigureServices(IServiceCollection services) { // Add EF services to the services container. services.AddEntityFramework(Configuration) .AddSqlServer() .AddDbContext<ApplicationDbContext>(); // Add Identity services to the services container. services.AddDefaultIdentity<ApplicationDbContext, ApplicationUser, IdentityRole>(Configuration); // Add MVC services to the services container. services.AddMvc(); // Uncomment the following line to add Web API servcies which makes it easier to port Web API 2 controllers. // You need to add Microsoft.AspNet.Mvc.WebApiCompatShim package to project.json // services.AddWebApiConventions(); } // Configure is called after ConfigureServices is called. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { // Configure the HTTP request pipeline. // Add the console logger. loggerfactory.AddConsole(); // Add the following to the request pipeline only in development environment. if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase)) { app.UseBrowserLink(); app.UseErrorPage(ErrorPageOptions.ShowAll); app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll); } else { // Add Error handling middleware which catches all application specific errors and // send the request to the following path or controller action. app.UseErrorHandler("/Home/Error"); } // Add static files to the request pipeline. app.UseStaticFiles(); // Add MVC to the request pipeline. app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); // Uncomment the following line to add a route for porting Web API 2 controllers. // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); }); }
ConfigureServices 在運行時的時候被運行,Configure 運行在 ConfigureServices 之后,查看 ConfigureServices 中的 Add 方法注釋,你會發現最后一個單詞總是 container(容器),這是怎么回事呢,其實就是往Ioc容器中注入類型依賴的對象,這些類型對象的管理都是在 Owin 管道中的,你只需要在 ConfigureServices 中使用 Add 方法注冊相應模塊就可以了,其他的東西 ASP.NET 5 會幫你完成,而 Configure 是什么作用呢?我自己覺得它是配置模塊的一個“配置”,用戶你使用中間件或者應用程序的一個配置,比如,你使用 app.UseCookieAuthentication 進行配置用戶驗證的一些操作,你查看 UseCookieAuthentication 的定義,會發現其命名空間為 Microsoft.AspNet.Builder.CookieAuthenticationExtensions,所在程序集為 CookieAuthenticationExtensions(Owin 中間件),查看 Configure 中其他 Use 使用,你同樣會發現命名空間都是 Microsoft.AspNet.Builder 開頭,之前說 Owin 是一種協定,Extensions 就是一種中間件和應用程序的擴展,但都必須符合此協定,這樣才會有無限可能。