目录
第6部分——从OWIN Katana到ASP.NET Core
[注意:您可以将下载的项目用作模板来启动OWIN Katana或WebAPI应用程序。
介绍
我被要求向不同的开发团队进行有关OWIN和Web API的介绍,因为他们想要对该技术进行介绍,而该团队中没有人以前接触过该技术。在此之前,我为公司开发了许多内部API。借助演示幻灯片和材料,我决定也将它们记录为博客文章,以便对其进行正确记录。与原始演示文稿相比,我投入了很多额外的信息,以覆盖更广泛的互联网受众。
即使最近对ASP.NET Core进行了革命性的更改,我仍然发现,了解OWIN对于理解ASP.NET Core的框架和功能至关重要。OWIN中有许多概念和体系结构设计仍然适用于ASP.NET Core。这篇文章主要关注OWIN和Katana,后者是OWIN的实现。详细解释了OWIN Katana的内部工作。之后,将分配一小章来讨论使用OWIN Katana进行Web API开发,最后是对ASP.NET Core的简要介绍。
第1部分——什么是OWIN
OWIN代表.NET的Open Web Interface。OWIN是一个开放标准规范,它定义了.NET Web服务器和Web应用程序之间的标准接口。目的是提供一种简单、可插拔且轻便的标准接口。
OWIN受到开发其他编码语言的Web框架的激励,例如JavaScript的Node.js,Ruby的Rack和Phyton的WSGI。所有这些Web框架都设计为快速、简单并且它们以模块化方式支持Web应用程序的开发。相反,在OWIN之前,每个.NET Web应用程序都需要对> System.Web.dll的依赖,该依赖关系与Microsoft的IIS(Internet信息服务)紧密耦合。这意味着.NET Web应用程序附带了许多来自IIS的应用程序组件堆栈,无论是否实际需要它们。这使.NET Web应用程序总体上较重,并且在许多基准测试中,它们的运行速度均比其他编码语言中的同类应用程序慢。
OWIN由Microsoft社区的成员发起;例如C#、F#和动态编程社区。因此,规范在很大程度上受到那些社区的编程范例的影响。从最初的原型制作开始,OWIN被简化为一个通用规范。它基本上指定,如图1所示:
- Web服务器和Web应用程序之间以及Web应用程序组件之间要传递的对象的类型。这也称为环境对象。
- Web应用程序组件的结构。结构应该简单,并以一致的方式表示。
除基础类语言(FCL)类型和支持异步处理的需求外,其他约束还包括没有依赖性。
图1. OWIN的范围 |
图2. OWIN规范(Web服务器和Web应用程序之间的交互) |
图2显示了OWIN对Web服务器和Web应用程序交互的概述,其中至少包含4个重要方面:
1、OWIN引入了一个键值对字典Dictionary<string, object>,作为要在Web服务器之间传递的环境对象和Web应用程序之间以及Web应用程序组件之间。OWIN还指定以下键:
必须的 | 键 | 类型(默认) |
yes | owin.RequestBody | Stream |
yes | owin.RequestHeaders | IDictionary<string, string[]> |
yes | owin.RequestMethod | string |
yes | owin.RequestPath | string |
yes | owin.RequestPathBase | string |
yes | owin.RequestProtocol | string |
yes | owin.RequestQueryString | string |
yes | owin.RequestScheme | string |
yes | owin.ResponseBody | Stream |
yes | owin.ResponseHeaders | IDictionary<string, string[]> |
no | owin.ResponseStatusCode | int(200) |
no | owin.ResponseReasonPhrase | string (OK) |
no | owin.ResponseProtocol | string (HTTP/1.1) |
yes | owin.CallCancelled | CancellationToken |
yes | owin.Version | string |
(来源:http://owin.org)
所有这些键值均不可为空,并且只能通过其序号(区分大小写)进行比较。Web服务器负责在收到http请求时构造此环境对象,并将其传递给Web应用程序。
2、每个Web应用程序组件都被建模为委托函数Func<IDictionary<string, object>,Task>,这意味着该组件接受一个环境对象IDictionary<string, object>,并返回awaitable Task。这也意味着委托支持异步调用。委托函数的名称为AppFunc,OWIN中的Web应用程序组件称为中间件。
3、管道模型用于构建中间件。不幸的是,OWIN 1.0没有指定中间件如何链接到管道中。后来针对OWIN 1.0的草案尝试包括中间件和管道构建器的规范。自2016年3月1日起,它是已过期的在建工程草案,似乎已被放弃。即使未指定,在实际实现中始终需要管道/中间件/应用程序构建器。
那么构建器注册中间件的要求是什么呢?流水线结构暗示中间件的实现可以选择调用其他或“下一个”中间件。例如,第一个中间件将调用下一个中间件,依此类推,直到最后一个直接终止或返回调用的中间件(第一个中间件通常称为直通中间件,最后一个被称为终止中间件)。为了能够将中间件作为“委托”函数链接到管道(委托是OWIN中的一等公民,主要受函数式编程范例的影响),构建器需要具有Func<AppFunc, AppFunc>签名的委托。第一个参数(AppFunc即参数)是next中间件,而第二个中间件(即返回)是中间件主体。如果中间件是一个直通中间件,那么中间件体将调用next中间件。
当构建器构建管道时,它以相反的顺序依次执行已注册的委托函数,并将最后一次执行的结果(中间件主体)作为下一次执行的参数(已执行的委托函数的参数'next'),直到检索到第一个中间件主体(AppFunc)并充当管道的入口点。对于函数式程序员(F#)来说,这是一个绝妙的解决方案,但是对于面向对象的程序员(C#),这可能会造成混乱,因为这种方法不是一种规范,并且与面向对象的思想不符,即使C#语言本身支持委托。稍后将在讨论使用Katana创建中间件时讨论此问题,Katana是C#中的OWIN实现。
OWIN明确区分了主机、服务器、中间件、Web框架和Web应用程序。但是,OWIN规范仅处理主机、服务器和中间件的一部分。以下是从OWIN网站获取的说明。
- Server—直接与客户端通信,然后使用OWIN语义处理请求的HTTP服务器。服务器可能需要转换为OWIN语义的适配器层。
- Web Framework— OWIN之上的独立组件,公开了自己的对象模型或API,应用程序可以使用它们来促进请求处理。Web框架可能需要从OWIN语义转换的适配器层。
- Web Application —使用OWIN兼容服务器运行的特定应用程序,可能建立在Web框架之上。
- Middleware —穿过组成服务器和应用程序之间管道的组件,以检查、路由或修改用于特定目的的请求和响应消息。
- Host—应用程序和服务器在其中执行的过程,主要负责应用程序的启动。某些服务器也是主机。
(来源:http://owin.org)
即使可以使用中间件管道编写整个基础的Web应用程序,该结构实际上也打算用作Web开发的基础结构。中间件特别适合于编写Web框架和Web应用程序的各个方面,例如错误处理、日志记录等,并且可以基于管道中注册的Web框架来编写Web应用程序。使用该结构还鼓励开发人员从Web应用程序中分离出各个方面。
4、为OWIN实现编写的Web应用程序,Web框架或中间件不必依赖任何IIS功能,并且可以将它们与任何理解OWIN语义并以不同方式托管的Web服务器一起插入。需要OWIN兼容服务器来实现特定于服务器规范的规则。因此,中间件可以对环境对象中必填字段的可用性做出假设。中间件还需要遵循错误或异常处理规则,以及编写响应主体和响应标头的顺序。值得阅读整个OWIN规范,并且不需要很长时间。
规范只是一个开始。另一个目标是鼓励为.NET开发简单且可重复使用的模块。期望可以将中间件作为nuget包下载的中间件有了显著增长。长期目标是激发.NET Web开发的开源生态系统。
第2部分——什么是Katana
OWIN有许多实现,例如C#社区的Katana和F#社区的Freya。还有许多仅实现托管和服务器OWIN规范的库,例如Nowin和Suave。这些列表在OWIN网站上。
Katana是Microsoft的OWIN规范的实现。它们采用nuget包的形式,它们都有一个命名空间Microsoft.Owin。但是,Katana不仅是OWIN的实现,还具有其他抽象来帮助提高开发效率。Katana添加了许多面向对象的构造,例如IOwinContext接口和OwinMiddleware抽象类。IOwinContext将被用作对环境对象的抽象和可通过采取IDictionary<string, object>对象作为构造函数参数来实例化为OwinContext,而OwinMiddleware用作创建中间件类的基类。实际上,OWIN尚未指定中间件或管道构建器的标准形式。但是,从OWIN中间件草案的外观来看,仅使用委托。相反,Katana作为OWIN的面向对象的C#实现,使用实现了接口IAppBuilder的AppBuilder类作为管道构建器。除此之外,Katana还允许将中间件创建为类,并且AppBuilder可以使用类类型或类实例将其注册。应该假定这些Katana的中间件类只能被AppBuilder使用
Katana还具有许多构造,以帮助访问环境对象,例如IOwinRequest和IOwinResponse,分别由OwinRequest和OwinResponse类实现。OWIN兼容的中间件仍然可以由有限的作者使用Katana的结构,如OwinContext,OwinRequest,OwinResponse中间件体。
此外,Katana还包含托管组件和侦听器组件,托管组件处理应用程序的服务器方面,例如创建侦听器或将接收到的http请求转换为符合OWIN语义的环境对象,并将该环境对象传递到中间件管道中。当与托管一起创建OWIN应用程序或进行自我托管时(例如,创建作为控制台应用程序运行的OWIN应用程序,winforms应用程序或Windows服务),托管和侦听器组件都是必需的。另一方面,如果OWIN应用程序由IIS托管,则只需要托管组件即可将http请求转换为环境对象并将其传递到中间件管道中。Katana还实现了一个名为“OwinHost'的托管,作为一个nuget程序包,可用于替换IIS。两者之间的区别在于IIS有自己的管道,而' OwinHost'没有管道。当使用IIS托管时,服务器或侦听器位于IIS基础结构中,并且OWIN应用程序在IIS集成管道模型下运行,其中IIS管道与OWIN中间件管道结合在一起,这将在下一部分中进行解释。以上所有托管组件均分为许多不同的程序包,因此可以有选择地使用它们来适合应用程序。
最后,Katana具有许多常见的中间件,例如CORS、诊断程序、静态文件、安全性以及帮助程序和实用程序类。以下是与Katana相关的重要程序集或DLL:
- Microsoft.Owin ——OWIN的Katana实现
- Microsoft.Owin.Host ——托管组件的公共库
- Microsoft.Owin.Host.HttpListener ——侦听器或服务器实现
- Microsoft.Owin.Host.SystemWeb ——IIS托管的托管组件
- Microsoft.Owin.Diagnostic ——诊断中间件
- Microsoft.Owin.Cors ——Cors中间件
- Microsoft.Owin.StaticFiles ——静态文件中间件
- Microsoft.Owin.Security ——用于安全和身份的通用库
- Microsoft.Owin.Security.* ——用于特定安全/身份模型的中间件,例如Cookies、活动目录、Facebook、Google、jwt、oauth、wsfederation、twitter、Microsoft Account, open ID connect
- Owin ——OWIN抽象
- OwinHost ——Katana主机实现
这些程序集通过一些nuget包来分发,例如,OwinHost,Microsoft.Owin.Host.SystemWeb和Microsoft.Owin.Host.SelfHost。Katana实现是开源的,可以从https://github.com/aspnet/AspNetKatana访问源代码。
第3部分——使用OWIN Katana进行应用程序开发
如前所述,OWIN Katana提供了一种编写Web应用程序的结构。诸如日志记录和异常处理之类的横切关注点以及诸如Web API之类的Web框架都可以编写为中间件。可以使用在管道中注册的Web框架中间件来编写主要的Web应用程序逻辑。在这一部分中,OWIN Katana应用程序开发归因于编写中间件,创建中间件管道以及启动侦听器或服务器的活动。下一部分将单独讨论使用Web API框架开发Web应用程序。
3.1. 创建侦听器或服务器
仅在自托管方案中才需要创建侦听器或服务器,而OwinHost或IIS托管模型则不需要。Katana提供了一个static方法,WebApp.Start从Microsfot.Owin.Hosting创建一个或多个服务器。创建服务器与创建侦听器并激活它们是相同的。有许多重载方法和通用方法可以做到这一点。
- IDisposable Start(string url, Action<IAppBuilder> startup)
- IDisposable Start(StartOptions startOptions, Action<IAppBuilder> startup)
- IDisposable Start<TStartup>(string url)
- IDisposable Start<TStartup>(StartOptions startOptions)
- IDisposable Start(StartOptions startOptions)
- IDisposable Start(string url)
在OWIN Katana中创建侦听器时,需要两个重要的信息,即侦听url和startup 委托,如第一个重载所示。侦听url是接收HTTP请求的端点,而startup具有签名的委托Action<IAppBuilder>是配置中间件管道的函数。
WebApp.Start方法不仅创建侦听器,还将其连接到中间件管道。此方法创建类型为IAppBuilder的中间件管道构建器,传递给要配置的startup委托,并构建中间件管道以获取管道中第一个中间件的AppFunc条目。startup委托可以采取许多形式:方法名(方法组)、委托变量或lambda 表达,如在下面的代码证实:
public class Program
{
public static void Main(string[] args)
{
//1. Using an existing method (method groups)
using (var host = WebApp.Start("http://localhost:9000", CustomConfiguration))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
//2.Using a delegate variable
Action<IAppBuilder> startup = CustomConfiguration;
using (var host = WebApp.Start("http://localhost:9000", startup))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
//2. Using a lambda expression
using (var host = WebApp.Start("http://localhost:9000",
builder => builder.Use(typeof(Middleware))))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
运行服务器应用程序时,该url,特别是端口不能被其他应用程序使用,并且运行该应用程序的用户帐户必须具有打开端口的权限。如果发生这种情况,则应用程序将抛出拒绝访问的异常。使用管理特权运行以下命令提示符。
netsh http>add urlacl http://[ipaddress]:[port]/ user=Everyone
使用第二种重载方法,可以通过设置StartOptions的Urls属性来创建多个监听urls。下面的示例代码演示了如何创建一个侦听多个urls的服务器。
public static void Main(string[] args)
{
StartOptions startOptions = new StartOptions();
startOptions.Urls.Add("http://localhost:9000");
startOptions.Urls.Add("http://localhost:9500");
startOptions.Urls.Add("http://localhost:9700");
using (var host = WebApp.Start(startOptions, CustomConfiguration))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
第三和第四泛型方法类似于第一和第二种方法,不同之处在于它们接受泛型startup类而不是startup委托。为了用作这些方法的startup类,该类不能为static且具有名称为Configuration的实例或static方法。此方法必须具有与startup委托相同的签名Action<IAppBuilder>。下面的代码显示了使用startup类创建服务器的示例。
public class Program
{
public static void Main(string[] args)
{
using (var host = WebApp.Start<CustomStartup>("http://localhost:9000"))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
}
public class CustomStartup
{
public static void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(.....);
}
}
第五个重载方法仅接受一个参数StartupOption。在startup类或委托可以在StartupOption的AppStartup属性中设定。该属性可以采用'static'或实例startup类名称。它也可以采用签名为Action<IAppBuilder>的任何方法名称。下面的示例代码说明了第五种重载方法的用法。
public class Program
{
public static void Main(string[] args)
{
StartOptions startOptions = new StartOptions();
startOptions.Urls.Add("http://localhost:9000");
startOptions.Urls.Add("http://localhost:9500");
startOptions.AppStartup = "Owin.Application.CustomStartup"; //1. Assign
// to a startup class name (CustomStartup class)
startOptions.AppStartup = "Owin.Application.CustomStartup.CustomConfiguration"; //OR 2.
// Assign to a method name (Configuration method of the CustomStartup class)
using (var host = WebApp.Start(startOptions))
{
Console.WriteLine("Start Listening...");
Console.ReadKey();
}
}
}
public static class CustomStartup
{
public static void CustomConfiguration(IAppBuilder appBuilder)
{
appBuilder
.Use(...);
}
}
如果AppStartup属性未设置,则该方法将自动检测一个startup类,这类似于不接受startup委托或startup类的六重或最终重载方法所执行的操作。以下列表是自动检测startup类的规则:
- Naming convention——Katana将查找static或带有名称Startup(区分大小写)的实例类,并在根(全局)命名空间或与程序集名称匹配的命名空间中包含名称为Configuration的static或实例方法。
- OwinStartupAttribute——此方法优先于命名约定。Katana将查找指定为assembly 级别的属性,例如[Assembly:OwinStartup(typeof(CustomStartup))]。可以在允许assembly属性的任何地方声明它。该属性具有三种重载方法。第一个与示例类似,采用startup类的类型。第二个则采用startup类的类型及其startup方法。第三个是一个友好的名称和一个startup类的类型。友好名称将与配置文件结合使用。
- Configuration file——该方法优先于OwinStartupAttribute方法。Katana将在应用程序配置文件中查找具有该appSettings部分下方owin:appStartup名称的键。通过指定类的全名,可以使用该键直接链接到startup类。或者,可以通过指定属性的友好名称来使用该键链接到OwinStartupAttribute。可以声明多个OwinStartupAttribute,每个都有不同的友好名称。然后,配置文件可以决定在owin:appStartup键中使用哪一个。
该应用程序还可以使用另一个配置键owin:AutomaticAppStartup(将其设置为“true”或“false”)来显式控制OWIN Startup类自动检测功能。可以在此MSDN文章中找到有关OWIN Startup类检测的详细说明。
3.2. 配置中间件管道
创建OWIN Katana应用程序的第二个元素是配置中间件管道。OWIN Katana提供了一个构建器抽象IAppBuilder,以允许开发人员配置管道。构建器在WebApp.Start方法内部实现AppBuilder创建,然后以构建器作为参数调用startup方法。因此,该startup方法的功能是配置管道。
IAppBuilder利用Use方法,其具有相同的签名Action<object middleware, object[] args>,来注册中间件。中间件对象是一种动态类型,可以采用多种形式,例如委托,类类型或类实例。此外,中间件可以选择具有一个或多个参数。下面的代码演示了带有loggingOptions参数的Logging中间件类的注册:
public void Configuration(IAppBuilder appBuilder)
{
var loggingOptions = new LoggingOptions()
{
Level = 1
};
appBuilder
.Use(typeof(Logging), loggingOptions);
}
下一节有关创建中间件的方法将详细讨论Use方法的用法。
此外,Katana为应用程序构建器IAppBuilder提供了许多扩展方法。一些重要的是:
- Use<T>(string[] args)
- Use(Func<IOwinContext, Func<Task> /*next*/, Task> handler)
- Run(Func<IOwinContext, Task> handler)
- Map(string pathMatch, Action<IAppBuilder> configuration)
- MapWhen(Predicate predicate, Action<IAppBuilder> configuration)
- MapWhenAsync(PredicateAsync predicate, Action<IAppBuilder> configuration)
如果将中间件实现为类而不是委托,则泛型方法Use<T>提供了一种注册中间件的替代方法。它可以选择接受一个或多个中间件参数。使用此方法,可以将以下代码示例编写为:
public void Configuration(IAppBuilder appBuilder)
{
var loggingOptions = new LoggingOptions()
{
Level = 1
};
appBuilder
.Use<Logging>(loggingOptions);
}
第二种和第三种方法提供了注册中间件的不同方法,它们使用的是Katana抽象而不是OWIN抽象。在该钩子下,这些方法注册了UseHandlerMiddleware中间件,并且将一个handler作为中间件参数。处理程序等效于中间件主体。第二种方法接受一个处理程序,它接受IOwinContext和'next'组件(Func<Task>),而Run方法的处理程序只接受IOwinContext。第三种方法仅应用于在管道中注册最后一个中间件或精确的中间件处理程序。下面是第二种和第三种方法的用法示例:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(PassThroughHandler)
.Run(TerminatingHandler);
}
public async Task PassThroughHandler(IOwinContext context, Func<Task> next)
{
System.Console("This is an inbound flow of a pass through middleware");
//the calling of below statement will call 'next'
//component of the underlying middleware 'UseHandlerMiddleware'
await next.Invoke();
System.Console("This is outbound an flow of a pass through middleware");
}
public async Task TerminatingHandler(IOwinContext context)
{
System.Console("This is a terminating middleware");
}
第四种或Map方法基于请求URI的路径匹配评估为管道提供了分支策略。在钩子之下,它使用MapMiddleware中间件。该中间件检查请求的URI Path是否以给定pathMatch参数开头。如果匹配,则中间件执行由方法的第二个参数委托配置的映射管道。中间件成为主管道的终止中间件。否则,中间件将继续执行到主管道中的下一个中间件。
当中间件执行进入映射管道时,pathMatch将添加到请求的URI BasePath中,并从请求的URI Path中减少。此外,使用Map方法时,pathMatch参数不应以' /'字符结尾。下面是Map方法用法的示例。
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Map("/Admin", ConfigurationForAdmin)
.Use(typeof(MainPipelineMiddleware));
}
public void ConfigurationForAdmin(IAppBuilder appbuilder)
{
appBuilder
.Use(typeof(AdminPipelineMiddleware));
}
MapWhen和MapWhenAsync方法类似于Map方法,不同之处在于使用其评估的谓词(predicate)委托,即返回一个布尔表达式,而不是使用路径匹配。该MapWhenAsync特别使用awaitable谓词(predicate)委托。在钩子之下,他们使用MapWhenMiddleware中间件。如果谓词(predicate)委托返回“true”,则中间件执行映射管道,否则它将继续执行到主管道中的下一个中间件。谓词(predicate)子句带有一个IOwinContext参数,它使您可以灵活地评估整个请求,而不仅仅是请求的URI Path。以下是MapWhen和MapWhenAsync方法用法的示例。
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.MapWhen(IsPostMethod, ConfigurationForPostMethod)
.MapWhenAsync(IsGetMethodAsync, ConfigurationForGetMethod)
.Use(typeof(MainPipelineMiddleware));
}
public bool IsPostMethod(IOwinContext context)
{
return context.Request.Method == "POST";
}
public async Task<bool> IsGetMethodAsync(IOwinContext context)
{
return await Task.FromResult(context.Request.Method == "GET");
}
public void ConfigurationForPostMethod(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(PostMethodPipelineMiddleware));
}
public void ConfigurationForGetMethod(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(GetMethodPipelineMiddleware));
}
当在管道中使用多个Map或MapWhen时,应首先放置更具体的约束,否则它们将永远没有机会被评估。
3.3. 创建中间件
编写中间件是OWIN Katana应用程序开发的一部分。该Use方法是将中间件注册到管道中的主要机制。该方法的签名是IAppBuilder Use(object middleware, params object[] args),它带有中间件对象和可选的中间件参数。所有参数都是动态对象,必须提供正确的参数类型,否则应用程序将抛出运行时异常。
可以将中间件创建为委托函数或类,并且可以通过多种方法将它们注册到管道中。下面是几种基于将中间件注册到管道中的机制创建中间件的方法。
3.3.1. 创建中间件委托,由委托注册
为了使委托有资格作为中间件对象,委托必须具有Func<AppFunc, AppFunc>的签名。第一个AppFunc(或委托的参数类型)表示'next'中间件组件,而第二个AppFunc(或委托的返回类型)是中间件主体。如果中间件还具有其他参数(在Katana术语中称为中间件选项),则委托签名必须为Func<AppFuc, args, AppFunc>。例如,如果中间件具有string和int参数,则委托必须为Func<AppFunc, string, int, AppFunc>。下面的示例代码说明了如何创建中间件并将其作为委托注册到管道中。
namespace MiddlewareUsingDelegate
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
Func<AppFunc, AppFunc> delegateMiddleware = new Func<AppFunc,
AppFunc>(MiddlewareMethod); //assign a delegate variable using a delegage constructor
delegateMiddleware = MiddlewareMethod; //OR assign a delegate variable
//using method groups
appBuilder
.Use(delegateMiddleware) //1. using a delegate variable
.Use((Func<AppFunc, AppFunc>)MiddlewareMethod) //2. using casting to a delegate
// from method groups (need casting
// or delegate constructor)
.Use(new Func<AppFunc, AppFunc>((next => //3. using a delegate constructor
// taken a lambda expression (need
// casting or delegate constructor)
{
//the line here is executed one time when pipeline is built
AppFunc middlewareMainComponent = async env =>
{
//the line here is executed for every http request
Console.WriteLine("Inbound with delegate");
await next.Invoke(env);
Console.WriteLine("Outbound with delegate");
};
return middlewareMainComponent;
})))
.Use((Func<AppFunc, string, AppFunc>)
MiddlewareMethodWithOptions, "Options"); //4. delegate with additional param args
}
public AppFunc MiddlewareMethod(AppFunc next)
{
//the line here is executed one time when pipeline is built
AppFunc middlewareBody = async (env) =>
{
//the line here is executed for every http request
Console.WriteLine("Inbound with delegate");
await next.Invoke(env);
Console.WriteLine("Outbound with delegate");
};
return middlewareBody;
}
public AppFunc MiddlewareMethodWithOptions(AppFunc next, string options)
{
//the line here is executed one time when pipeline is built
AppFunc middlewareBody = async (env) =>
{
//the line here is executed for every http request
Console.WriteLine(string.Format("Inbound with delegate {0}", options));
await next.Invoke(env);
Console.WriteLine(string.Format("Outbound with delegate {0}", options));
};
return middlewareBody;
}
}
}
前三个注册基本上执行相同的操作。第一个在将委托传递给Use方法之前,首先将委托分配给变量。可以从委托构造或方法组分配或lambda表达式(未显示)分配变量。方法组和lambda表达式不能直接在Use方法中使用。首先需要通过委托构造或转换(2和3)将它们转换为委托。方法4显示了使用中间件选项注册中间件委托。委托或中间件方法可以分为两个执行块。构建管道后,第一个块将执行一次。对于每个收到的http请求,将执行第二个块或中间件主体。
正如上一节中所讨论的,提供Katana扩展方法Use和Run,其可以分别接受的中间件处理程序委托Func<IOwinContext, Func<Task>, Task>和Func<IOwinContext, Task>。之所以称为处理程序,是因为它们已经使用了UseHandlerMiddleware中间件。使用这些方法,可以直接在UseorRun方法中将这些处理程序注册为方法组或lambda表达式。
3.3.2. 创建一个中间件类,按类类型注册
为了使一个类有资格作为中间件对象并可以通过其类类型进行注册,该类必须具有一个构造函数,该构造函数至少接受AppFunc代表'next'中间件组件的委托。该类在构造函数中可以具有其他参数,并且在注册中间件时,必须将相应的参数作为args参数传递。此外,该类必须具有Func<Task, AppFunc>签名为Invoke的方法。下面的示例代码显示了使用中间件类类型进行中间件的注册,这可以通过使用typeof运算符(1)来完成。此外,如果中间件具有关联的构造函数(2),则可以提供args参数。
namespace MiddlewareUsingClassType
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(ClassTypeMiddleware)) //1. Typeof operator
.Use(typeof(ClassTypeMiddleware), "Options"); //2. Typeof operator,
// with an args parameter
}
}
public class ClassTypeMiddleware
{
private AppFunc _next;
private string _option;
public ClassTypeMiddleware(AppFunc next)
{
_next = next;
}
public ClassTypeMiddleware(AppFunc next, string option)
{
_next = next;
_option = option;
}
public async Task Invoke(IDictionary<string, object> env)
{
Console.WriteLine(string.Format("Inbound with class type {0}", _option));
await _next.Invoke(env);
Console.WriteLine(string.Format("Outbound with class type {0}", _option));
}
}
}
另外,Katana还可以通过从OwinMiddleware抽象类派生而提供自己的抽象来实现中间件类,并且可以通过其类类型将其注册到管道中。其构造函数使用OwinMiddleware代替AppFunc,而Invoke方法采用IOwinContext代替IDictionary<string, object>。下面是使用Katana中间件类创建和注册中间件的示例代码。方法2是带有中间件参数的示例。
namespace MiddlewareUsingKatanaClassType
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(typeof(KatanaClassTypeMiddleware)) //1. Katana middleware class
.Use(typeof(KatanaClassTypeMiddleware), "Options"); //2. Katana middleware class
// with a middleware argument
}
}
public class KatanaClassTypeMiddleware : OwinMiddleware
{
private string _options;
public KatanaClassTypeMiddleware(OwinMiddleware next) : base(next)
{
Next = next;
}
public KatanaClassTypeMiddleware(OwinMiddleware next, string options) : base(next)
{
Next = next;
_options = options;
}
public override async Task Invoke(IOwinContext context)
{
Console.WriteLine(string.Format("Inbound with Katana middleware {0}", _options));
await Next.Invoke(context);
Console.WriteLine(string.Format("Outbound with Katana middleware {0}", _options));
}
}
}
如前一节所述,Katana提供了一种泛型方法Use<T>来替代使用typeof运算符。此方法可用于本节中讨论的所有中间件类。上一个示例中的中间件注册可以重写如下:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use<KatanaClassTypeMiddleware>() //1. Katana middleware class
.Use<KatanaClassTypeMiddleware>("Options"); //2. Katana middleware class
// with a middleware argument
}
3.3.3. 创建中间件类,并通过类实例进行注册
为了使一个类有资格作为中间件对象并可以通过其类实例进行注册,相应的类必须:
- 有一个Initialize方法,该方法接受一个AppFunc,表示'next'中间件组件,以及可选的中间件参数,以及一个带有Func<IDictionary<string, object>, Task>的签名的Invoke方法。
- 有一个签名为Func<AppFunc, args, AppFunc>的Invoke方法,其中args用于中间件参数
下面的示例代码说明了使用Initialize方法(1&2)和不使用Initialize方法(3&4)的中间件类。方法2和4演示了带有中间件参数的中间件类。对于方法2,该类必须具有签名为Action<AppFunc, args>的Initialize方法。对于方法4,该类必须具有签名为Function<AppFunc, args, AppFunc>的Invoke方法。
namespace MiddlewareUsingClassInstance
{
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.Use(new ClassInstanceMiddlewareWithInitialize()) //1. class instance
// with Initialize
.Use(new ClassInstanceMiddlewareWithInitialize(), "Options") //2. class instance
// with Initialize having additional argument
.Use(new ClassInstanceMiddlewareNoInitialize()) //3. class instance
// No Initialize
.Use(new ClassInstanceMiddlewareNoInitialize(), "Options"); //4. class instance
// No Initialize having additional argument
}
}
public class ClassInstanceMiddlewareWithInitialize
{
private AppFunc _next;
private string _options;
public void Initialize(AppFunc next)
{
_next = next;
}
public void Initialize(AppFunc next, string options)
{
_next = next;
_options = options;
}
public async Task Invoke(IDictionary<string, object> env)
{
Console.WriteLine(string.Format
("Inbound with class instance (with Initialize) {0}", _options));
await _next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbound with class instance
(with Initialize) {0}", _options));
}
}
public class ClassInstanceMiddlewareNoInitialize
{
public AppFunc Invoke(AppFunc next)
{
return async (env) =>
{
Console.WriteLine(string.Format("Inbound with class instance (No Initialize)"));
await next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbond with class instance (No Initialize)"));
};
}
public AppFunc Invoke(AppFunc next, string options)
{
return async (env) =>
{
Console.WriteLine(string.Format
("Inbound with class instance (No Initialize) {0}", options));
await next.Invoke(env).ConfigureAwait(false);
Console.WriteLine(string.Format("Outbond with class instance
(No Initialize) {0}", options));
};
}
}
}
创建中间件时,通常的做法是在其IAppBuilder上创建扩展方法以注册中间件。好处是代替了使用原始Use(object middleware, params object[] args)方法,开发人员可以使用更有意义的方法UseXXX(param object[] args),或者UseXXX(),如果中间件没有中间件参数(用中间件名称替换XXX,例如Logging,ExceptionHandling等)。
3.4. IIS集成管道
如前所述,Katana不仅是OWIN实现。尽管OWIN的最初目的是从IIS中删除应用程序依赖项,但Katana提供了一种使用IIS托管Web应用程序的机制。前面的所有示例都是根据自托管的上下文编写的。那么,如果OWIN Katana应用程序是由IIS实现并托管的,那会有什么不同呢?至少存在一些差异:
- 使用IIS托管时,侦听器和服务器应用程序由IIS基础结构创建和维护。因此,无需创建侦听器。但是,要允许侦听器将http请求转换为OWIN语义并自动检测OWIN Startup类,则OWIN库Microsoft.Owin.Host.SystemWeb必须位于应用程序bin文件夹中。这可以通过引用nuget包Microsoft.Owin.Host.SystemWeb在项目中完成
- 由于无处定义startup方法,因此应用程序将使用自动检测OWIN Startup类机制,这已在上一部分中进行了讨论。
- 如果应用程序是由IIS服务器(而不是IIS Express)托管的,则该应用程序需要在IIS集成应用程序池中运行。
- 当使用IIS托管时,OWIN Katana管道与IIS管道集成在一起,因此,该模型也称为IIS集成管道。OWIN Katana管道仍将是主要入口点。自托管的区别在于OWIN Katana管道可以与IIS管道交错。以下是IIS管道的所有阶段。
public enum PipelineStage
{
Authenticate = 0,
PostAuthenticate = 1,
Authorize = 2,
PostAuthorize = 3,
ResolveCache = 4,
PostResolveCache = 5,
MapHandler = 6,
PostMapHandler = 7,
AcquireState = 8,
PostAcquireState 9,
PreHandlerExecute = 10
}
UseStageMarker方法用于在标记阶段需要执行标记之前告诉所有中间件。默认情况下,所有OWIN Katana中间件都在IIS PreHandlerExecute阶段执行,就像UseStageMarker在管道的末尾那样。例如,如果OWIN katana中间件管道如下所示:
public void Configuration(IAppBuilder app)
{
app
.Use<MiddlewareOne>()
.UseStageMarker(PipelineStage.Authenticate);
.Use<MiddlewareTwo>()
.UseStageMarker(PipelineStage.PostMapHandler);
.Use<MiddlewareThree>()
.Run<MiddlewareFour>();
}
上面的OWIN Katana中间件将与IIS管道结合使用,排序如下:
- Authenticate阶段:MiddlwareOne,IIS功能
- PostAuthenticate 阶段:IIS功能
- Authorize 阶段:IIS功能
- PostAuthorize 阶段:IIS功能
- ResolveCache 阶段:IIS功能
- PostResolveCache 阶段:IIS功能
- MapHandler 阶段:IIS功能
- PostMapHandler阶段:MiddlewareTwo,IIS功能
- AcquireState 阶段:IIS功能
- PostAcquireState 阶段:IIS功能
- PreHandlerExecute阶段:MiddlewareThree, MiddlewareFour
- ExecuteRequestHandler 阶段:仅IIS功能(此处没有OWIN中间件)
IIS功能代表可以通过IIS或Web配置进行配置的IIS中间件,并且在执行时可能处于活动状态,也可能不处于活动状态。因为IIS集成管道模型的执行遵循IIS阶段,所以阶段标记应在管道配置中以正确的顺序放置。如果某些阶段标记发生故障,它将被忽略并且不会引发异常。可以在下面的MSDN文章中找到更详细的解释。
此外,OWIN中间件只能放在PreHandlerExecute阶段上,如果OWIN管道中的最后一个中间件调用了下一个中间件,它将继续使用IIS功能(例如DirecttoryListingModule等)。如果开发人员不知道,这可能会导致预期的行为,很可能是HTTP Error 403.14 - Forbidden错误。解决方案是确保OWIN管道中的最后一个中间件是终止的中间件,该中间件不调用下一个中间件。否则,需要激活IIS功能(例如静态文件或目录列表),以使它们不会失败。
第4部分——托管OWIN Katana应用程序
在开发OWIN Katana应用程序时,您将需要决定如何运行或托管该应用程序。OWIN Katana应用程序可以作为控制台应用程序、桌面应用程序(例如WinForms应用程序)或Windows服务运行。该应用程序负责创建服务器或侦听器,这种托管方案称为自托管。或者,该应用程序可以作为Web应用程序与IIS托管在一起,或者以更轻量的OwinHost形式托管。本部分将探讨所有这些托管方案。这些项目是使用Visual Studio Enterprise 2017 Update 3创建的。大多数项目模板的名称中都包含目标框架(例如.NET Framework,.NET Core,.NET Standard)。较低版本的Visual Studio通常在其项目模板名称中没有目标框架,并且默认情况下没有目标.NET Framework。OWIN Katana依赖于.NET Framework,因此,本部分中的所有项目都针对.NET Framework。
4.1. 自托管为控制台应用程序
作为控制台应用程序自托管,侦听器或服务器可以在程序启动时或由命令行交互触发时启动。下面的项目演示了前一个。
1、创建一个控制台应用程序(.NET Framework)项目,并命名为Owin.SelfHosting.Console。如果使用其他版本的Visual Studio,请使用等效的项目模板。
2、添加对nuget包Microsoft.Owin.SelfHost的引用,这将为项目添加以下引用。
- Owin
- Microsoft.Owin
- Microsoft.Owin.Diagnostic
- Microsoft.Owin.Hosting
- Microsoft.Owin.Host.HttpListener
- Microsoft.Owin.Host.SelfHost
3、在一个单独的文件中创建一个WelcomeOptions类。此类在本WelcomeMiddleware类的后面使用。
namespace Owin.SelfHosting.Console
{
internal class WelcomeOption
{
public string HostName { get; set; }
public string Welcome { get; set; }
public WelcomeOption(string hostName, string welcome)
{
HostName = hostName;
Welcome = welcome;
}
}
}
4、在一个单独的文件中创建一个WelcomeMiddleware类。此类在本AppBuilderExtension类的后面使用。
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
namespace Owin.SelfHosting.Console
{
internal class WelcomeMiddleware : OwinMiddleware
{
private readonly WelcomeOption _option;
public WelcomeMiddleware(OwinMiddleware next, WelcomeOption option) : base(next)
{
_option = option;
}
public override async Task Invoke(IOwinContext context)
{
System.Console.WriteLine
("Http request received at " + DateTime.UtcNow.ToString());
await Next.Invoke(context);
string welcome = string.Format("I am {0}. {1}{2}",
_option.HostName, _option.Welcome, Environment.NewLine);
await context.Response.WriteAsync(welcome).ConfigureAwait(false);
}
}
}
5、在一个单独的文件中创建一个AppBuilderExtensions类。此类包含扩展方法,这些扩展方法使开发人员可以使用UseWelcome方法而不是泛型Use方法注册WelcomeMiddleware。第一个方法不带任何参数,并通过传递默认值来调用第二个方法。
namespace Owin.SelfHosting.Console
{
internal static class AppBuilderExtensions
{
public static IAppBuilder UseWelcome(this IAppBuilder appBuilder)
{
return appBuilder.UseWelcome(new WelcomeOption("Peter", "Welcome to this site"));
}
public static IAppBuilder UseWelcome
(this IAppBuilder appBuilder, WelcomeOption option)
{
return appBuilder
.Use(typeof(WelcomeMiddleware), option);
}
}
}
6、在一个单独的文件中创建一个Startup类。创建侦听器时需要此类。
namespace Owin.SelfHosting.Console
{
internal class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder
.UseWelcome();
}
}
}
7、在Program类中,将代码放在下面:
using Microsoft.Owin.Hosting;
namespace Owin.SelfHosting.Console
{
internal class Program
{
public static void Main(string[] args)
{
string hostUrl = "http://localhost:9000/console";
using (WebApp.Start<Startup>(""))
{
System.Console.WriteLine
(string.Format("Start Listening at {0} ...", hostUrl));
System.Console.ReadKey();
}
}
}
}
8、运行该应用程序,然后等待其显示 'Start Listening at http://localhost:9000/console ...'
9、要测试该应用程序,请运行任何浏览器并输入'http://localhost:9000/console'
10、浏览器应显示'I am Peter. Welcome to this site' ,控制台应用程序应显示'Http request received at [request timestamp]'
4.2. 自托管为WinForms应用程序
作为Winforms应用程序进行自我托管,服务器或侦听器可以在程序启动时或通过UI交互触发时启动。下面的项目演示了后者。应用程序显示一个表单,其中包含一个Start 按钮和一个显示服务器活动日志的Textbox。单击Start 按钮时,服务器启动。自托管作为控制台应用程序的唯一区别是Program类中以前的代码已移至Start按钮的事件处理程序中。
1、创建一个Windows Forms App (.NET Framework)项目并命名Owin.SelfHosting.WinForms。如果使用其他版本的Visual Studio,请使用等效的项目模板。
2、重命名Form1为Main,并将表单的Text属性更改为“Main”。
3、在Main表单,创建一个Text设置为“Start”的按钮btnStart,并创建一个Multiline设置为“True”的文本框txtLog。
4、从4.1自托管为控制台应用程序开始执行步骤2到步骤6,但将命名空间更改为Owin.SelfHosting.WinForms。
5、在一个单独的文件中创建一个ControlWriter类。此类的功能是将System.Console输出重定向到一个UI控件,它是这个项目的txtLog。
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
//modified from answer to
//https://stackoverflow.com/questions/18726852/redirecting-console-writeline-to-textbox
namespace Owin.SelfHosting.WinForms
{
public class ControlWriter : TextWriter
{
private Control textbox;
public ControlWriter(Control textbox)
{
this.textbox = textbox;
}
public override void Write(string value)
{
if (textbox.InvokeRequired)
{
textbox.Invoke(new Action<char>(Write), value);
}
else
{
textbox.Text += value;
}
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
}
6、在btnStart点击事件处理程序中,放下面的代码。该btnStart按钮函数用作启动或停止侦听器的开关。启动时,它还会将System.Console输出重定向到txtLog。
private IDisposable _server;
private void btnStart_Click(object sender, EventArgs e)
{
if (btnStart.Text == "Start")
{
txtLog.Clear();
System.Console.SetOut(new ControlWriter(txtLog));
string hostUrl = "http://localhost:9000/winforms";
_server = WebApp.Start<Startup>(hostUrl);
System.Console.WriteLine(string.Format("Start listening at {0} ...", hostUrl));
btnStart.Text = "Stop";
}
else
{
_server.Dispose();
btnStart.Text = "Start";
txtLog.Clear();
}
}
7、运行该应用程序,然后单击Start按钮,然后等待直到txtLog显示'Start Listening at http://localhost:9000/winforms ...'
8、要测试该应用程序,请运行任何浏览器并输入'http://localhost:9000/winforms'
9、浏览器应显示'I am Peter. Welcome to this site',并且txtLog应显示'Http request received at [request timestamp]'
4.3. 自托管作为Windows服务
为了自托管为Windows服务,在服务启动时将创建侦听器,并在服务停止时将其释放。
1、创建一个Console App(.NET Framework)项目并命名Owin.SelfHosting.WindowsService。如果使用其他版本的Visual Studio,请使用等效的项目模板。
2、从4.1 自托管作为控制台应用程序开始执行步骤2到步骤6 ,但将命名空间更改为Owin.SelfHosting.WindowsService。
3、添加对框架程序集System.ServiceProcess的引用。
4、添加一个新类型的项目'Windows Service',并为其命名OwinService。这将在单独的文件中创建一个OwinService类。使用以下代码对其进行修改:
using System;
using System.ServiceProcess;
using Microsoft.Owin.Hosting;
namespace Owin.SelfHosting.WindowsService
{
partial class OwinService : ServiceBase
{
private IDisposable _server;
public OwinService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
string hostUrl = "http://localhost:9000/windowsservice";
_server = WebApp.Start<Startup>(hostUrl);
}
protected override void OnStop()
{
_server.Dispose();
}
}
}
5、双击解决方案资源管理器中的OwinService以显示设计器表单,右键单击设计器表单并选择'Add Installer',然后将创建一个ProjectInstaller类
6、双击解决方案资源管理器中的ProjectInstaller,以显示设计器表单,其中将包含两个组件:serviceProcessIntaller1和serviceInstaller1
7、将serviceProcessInstaller1的Account属性设置为' LocalSystem'
8、确保serviceInstaller1的ServiceName设置为'OwinService’。可选的,serviceInstaller1的DisplayName和StartType可以为Windows服务的首选项设置。
9、编译项目,然后找到输出目录。默认情况下,它应该位于“bin/Debug”或“bin/Release”文件夹中。
10、运行'installUtil.exe Owin.SelfHosting.WindowsService.exe'注册OwinService。可以在.NET Framework文件夹%WINDIR%\Microsoft.NET\Framework[64]\v[framework_version]中找到InstallUtil。在我的机器上,此文件的位置为“C:\Windows\Microsoft.NET\Framework\v4.0.30319\”。
11、检查服务列表或在命令提示符下键入'Services.msc'并找到'OwinService',选择并启动它。
12、要测试该服务,请运行所有浏览器并输入'http://localhost:9000/windowsservice'。
13、浏览器应显示 'I am Peter. Welcome to this site'
4.4. IIS主机
1、创建一个ASP.NET Web Application (NET. Framework)项目,然后选择一个Empty template,然后命名Owin.IISHosting。如果使用其他版本的Visual Studio,请使用等效的项目模板。
2、添加对nuget包Microsoft.Owin.Host.SystemWeb的引用,这将为项目添加以下引用:
- Owin
- Microsoft.Owin
- Microsoft.Owin.Host.SystemWeb
3、按照“自托管为控制台应用程序”中的步骤3至步骤6进行操作,但将命名空间更改为Owin.IISHosting,并对其WelcomeMiddleware类进行修改以确保其不会调用下一个中间件。
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
namespace Owin.IISHosting
{
internal class WelcomeMiddleware : OwinMiddleware
{
private readonly WelcomeOption _option;
public WelcomeMiddleware(OwinMiddleware next, WelcomeOption option) : base(next)
{
_option = option;
}
public override async Task Invoke(IOwinContext context)
{
System.Console.WriteLine
("Http request received at " + DateTime.UtcNow.ToString());
//await Next.Invoke(context); //For IIS Integrated pipeline,
//the last middleware should be non passthrough
string welcome = string.Format("I am {0}. {1}{2}",
_option.HostName, _option.Welcome, Environment.NewLine);
await context.Response.WriteAsync(welcome).ConfigureAwait(false);
}
}
}
4、在设计视图中打开项目属性,在'Web'选项卡中,在'Server'部分,检查'apply server settings to all users (store in project file)',从下拉列表框中选择'IIS Express',并在'Project Url'中输入'http://localhost:9000/iishosting'。
5、从Visual Studio运行该应用程序,将启动IIS Express并托管该应用程序。您可以通过单击系统任务栏中的IIS Express图标来控制运行的应用程序。
6、Visual Studio还将启动浏览器并指向'http://localhost:9000/iishosting',它应该显示'I am Peter. Welcome to this site'。
4.5. 用OwinHost托管
Katana还附带了一个nuget软件包OwinHost,其中包含替代托管的实现,可用于替换IIS托管。当您创建一个ASP Web应用程序项目并添加此程序包时,它将为该项目添加一个叫'OwinHost'的自定义托管。然后可以将该应用程序配置为使用'Owin Host'而不是'IIS Express'来运行。
- 从4.4 IIS托管步骤1到步骤3开始执行,但将命名空间更改为Owin.OwinHosting。
- 安装nuget软件包OwinHost(3.1.0),这将添加对该软件包的引用,并在'WebProjectProperties'项目文件(.csproj)中添加'Servers'条目。此必不可少的功能将添加'OwinHost'为自定义托管。
- 在设计视图中打开项目属性,在'Web'选项卡中,在'Server'部分,检查'apply server settings to all users (store in project file)',从下拉列表框中选择'OwinHost',并在'Project Url'中输入'http://localhost:9000/owinhosting'。
- 从Visual Studio运行该应用程序,然后'OwinHost'应当启动并托管该应用程序。
- Visual Studio还将启动浏览器并指向'http://localhost:9000/owinhosting',它应该显示'I am Peter. Welcome to this site'。
当使用OwinHost托管时,可以使用nuget软件包Microsoft.Owin.SelfHost而不是使用软件包Microsoft.Owin.Host.SystemWeb。OwinHost托管可以与在两个侦听器Microsoft.Owin.Host.SystemWeb和Microsoft.Owin.Host.HttpListener(这是在Microsoft.Owin.SelfHost包中)一起工作。
第5部分——使用OWIN Katana开发Web API
尽管可以使用OWIN Katana开发RESTful API,但它没有提供足够的杠杆作用或抽象来有效地做到这一点。用OWIN Katana编写中间件最适合Web框架或Web应用程序的交叉需求。开发RESTful API时,最好使用本部分将使用的Web框架,例如Nancy或Microsoft Web API。
Web API可以用于ASP.NET应用程序和OWIN Katana应用程序。使用OWIN Katana进行开发的好处是可以灵活地从各种托管方案中进行选择,如上一部分所述。ASP.NET(最高4.6)应用程序只能由IIS托管。两者之间的另一个主要区别是注册Web API中间件的入口点。对于OWIN Katana应用程序,它是在startup方法或Startup类中注册的,而对于ASP.NET应用程序,它是在Global.asax的Application_Startup方法中注册的。但是,后来称为ASP.NET Core的ASP.NET 5,应用程序已经在使用Startup类,其遵循OWIN Katana' style'。
使用OWIN Katana实现Web API时,Web API只是管道中的中间件,可以使用一个带有HttpConfiguration参数的UseWebApi方法进行注册。Web API提供了开发API时可以使用的高级构造,例如:控制器、路由器、格式化程序、查询解析模型绑定,并且大多数可以通过HttpConfiguration配置。
以下链接显示了Web API(Web API 2.0)内部的管道结构。WebAPI的第一阶段HttpMesageHandlers包含三种类型的处理程序。类似于中间件的Delegating处理程序可以通过HttpConfiguration注入。HttpRoutingDispatcher处理程序支持管道分支,可以通过HttpConfiguration被配置。最后一个处理程序HttpControllerDispatcher将请求路由到正确的控制器。Web API控制器必须从ApiController类派生。
WebAPI的第二阶段是控制器阶段。在执行控制器方法之前,需要进行很多处理。如果存在则执行Authentication和Authorization过滤器。接下来,模型绑定尝试将请求(URI、标头和正文)中的值映射到参数,然后将其传递到控制器中。然后,如果存在Action过滤器,则在控制器方法之前执行过滤器。在输出过程中,将执行Action过滤器,结果转换和Exception过滤器。在以下部分中,将使用自托管和IIS托管创建简单的Web API项目。
5.1. 具有自托管功能的Web API
Web API自托管作为控制台应用程序。
1、创建一个Console App (.NET Framework)项目,为其命名Owin.WebApi.SelfHosting。如果使用其他版本的Visual Studio,请使用等效的项目模板。
2、安装nuget软件包Microsoft.AspNet.WebApi.OwinSelfHost(3.1.0),它将添加以下引用:
- Microsoft.AspNet.WebApi.Client
- Microsoft.AspNet.WebApi.Core
- Microsoft.AspNet.WebApi.Owin
- Microsoft.AspNet.WebApi.OwinSelfHost
- Microsoft.Owin
- Microsoft.Owin.Host.HttpListener
- Microsoft.Owin.Hosting
- Newtonsoft.Json
- Owin
或者,安装nuget软件包Microsoft.AspNet.WebApi.Owin(5.2.3)和Microsoft.Owin.SelfHost(3.1.0),这将添加以下引用:
- Microsoft.AspNet.WebApi.Client
- Microsoft.AspNet.WebApi.Core
- Microsoft.AspNet.WebApi.Owin
- Microsoft.Owin
- Microsoft.Owin.Diagnostics
- Microsoft.Owin.Host.HttpListener
- Microsoft.Owin.Hosting
- Microsoft.Owin.SelfHost
- Newtonsoft.Json
- Owin
3、在一个单独的文件中创建一个Startup类。该startup方法将向已配置的HttpConfiguration注册Web API。MapHttpAttributeRoutes表示基于控制器上的路由属性的API路由。
using System.Web.Http;
namespace Owin.WebApi.SelfHosting
{
internal class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes();
httpConfig.EnsureInitialized();
appBuilder
.UseWebApi(httpConfig);
}
}
}
4、通过从ApiController类派生来创建一个UserController。用RoutePrefix属性标记类,并使用Route和HttpGet属性标记方法。该调用的路由为GET '/User/1234'。模型绑定将在'/User'之后为'identifier'赋值。如果identifier = 1234,则此方法返回json响应,否则返回NotFound响应。
using System.Web.Http;
namespace Owin.WebApi.SelfHosting
{
[RoutePrefix("User")]
public class UserController : ApiController
{
[Route("{identifier}")]
[HttpGet]
public IHttpActionResult GetUser(string identifier)
{
if (identifier == "1234")
{
return Json(new
{
Givenname = "Peter",
Surname = "Smith",
Age = 45
});
}
return NotFound();
}
}
}
5、在一个单独的文件中创建一个Program类。
using System;
using Microsoft.Owin.Hosting;
using Owin.WebApi.SelfHosting;
namespace Owin.WebApi.SelfHost
{
internal class Program
{
public static void Main(string[] args)
{
string hostUrl = "http://localhost:9000/webapi/selfhosting";
using (var server = WebApp.Start<Startup>(hostUrl))
{
Console.WriteLine(string.Format("Start Listening at {0} ...", hostUrl));
Console.ReadKey();
}
}
}
}
6、运行该应用程序,等待其显示 'Start Listening at http://localhost:9000/webapi/selfhosting ...'
7、要测试该应用程序,请运行任何浏览器并输入 'http://localhost:9000/webapi/selfhosting/user/1234'
8、浏览器应显示一个包含用户详细信息的json响应
5.2. 带有IIS托管的Web API
- 创建一个ASP.NET Web Application (.NET Framework)项目,选择一个Empty模板。如果使用其他版本的Visual Studio,请使用等效的项目模板。
- 有两个要安装的nuget软件包。首先,添加Microsoft.Owin.Host.SystemWeb程序包,这将添加以下引用:
- Microsoft.Owin
- Microsoft.Owin.Host.SystemWeb
- Owin
- 其次,添加Microsoft.AspNet.WebApi.Owin程序包,这将添加以下引用:
- Microsoft.AspNet.WebApi.Client
- Microsoft.AspNet.WebApi.Core
- Microsoft.AspNet.WebApi.Owin
- Newtonsoft.Json
- 从5.2. 具有自托管功能的Web API执行步骤3和步骤4 ,但将命名空间更改为Owin.WebApi.IISHosting
- 在设计视图中打开项目属性,在'Web'标签中,在'Server'部分,检查'apply server settings to all users (store in project file)',从下拉列表框中选择'IIS Express',并在'Project Url'中输入'http://localhost:9000/webapi/iishosting'。
- 要测试该应用程序,请运行所有浏览器并输入'http://localhost:9000/webapi/iishosting/user/1234'。
- 浏览器应显示一个包含用户详细信息的json响应。
第6部分——从OWIN Katana到ASP.NET Core
近年来,.NET Web开发技术发生了许多变化。与其他语言的Web开发保持一致,趋势是开发更小、更集中的组件,这些组件会更频繁地发布。
OWIN是朝着更加模块化、轻量级、开放平台和开源Microsoft Web产品迈进的架构设计的开始和基础。Katana是Microsoft尝试提供开发人员可以体验的实现。来自Katana实现和用法的反馈被反馈到下一个Microsoft Web产品ASP.NET 5的开发中。
ASP.NET 5于2015年首次发布。它将来自Web API和MVC 5的最佳功能组合到一个称为MVC6的产品中。同时,微软还推出了一个名为.NET Core 5的新框架,该框架还针对Linux和MacOS等其他平台。.NET Core Framework是精简版.NET Framework,并作为一组nuget软件包进行分发。ASP.NET 5可以在.NET Core 5和.NET Framework上运行。
2016年1月开始,ASP.NET 5名称更改为ASP.NET Core,此举与以前的仅针对.NET Framework的Microsoft ASP.NET 4.6有所不同。目前,ASP.NET Core 2产品于2017年8月与.NET Core 2 Framework一起发布,并在Visual Studio 2017更新版本3中受支持。但是.NET Core 2 Framework需要单独下载。
ASP.NET Core本质上支持OWIN,但不支持依赖于.NET Framework的Katana实现。尽管ASP.NET Core在OWIN和Katana中遵循相同的概念,也具有其自己的抽象。例如,它使用IApplicationBuilder而不是IAppBuilder,HttpContext而不是IDictionary<string, object>。
ASP.NET Core 2可以使用nuget包 Microsoft.AspNetCore.Owin中的UseOwin方法注册OWIN中间件。使用此方法,只能将纯粹的OWIN中间件(以Func<AppFunc, AppFunc>委托形式)添加到管道中。如下代码所示,委托参数pipeline用于注册OWIN中间件。
using AppFunc = Func<IDictionary<string, object>, Task>;
...
public void Configure(IApplicationBuilder appBuilder)
{
appBuilder
.UseOwin(pipeline=>
{
pipeline(OwinMiddlewareOne);
pipeline(OwinMiddlewareTwo);
});
}
public AppFunc OwinMiddlewareOne(AppFunc next)
{
AppFunc middlewareBody = async (env) =>
{
...
await next.Invoke(env);
...
}
return middlewareBody;
}
public AppFunc OwinMiddlewareTwo(AppFunc next)
{
AppFunc middlewareBody = async (env) =>
{
...
await next.Invoke(env);
...
}
return middlewareBody;
}
OWIN Katana和Web API实现也有许多变化和偏差,例如:
- 使用静态方法WebApp.Start启动服务器或监听器改变以使用IWebHost的Run()或Start()。它必须 在运行对象之前先建立WebHostBuilder对象。UseUrls和UseStartup<T>用来指定监听地址和各自的Startup类。该WebHostBuilder也有很多其他的方法。例如,构建器可以指定托管模式,比如通过IIS集成UseIISIntegration,通过UseConfiguration配置,通过UseServer服务器类型通过UseEnvironment环境,通过UseContentRoot和UseWebRoot上下文根或网站根目录或捕获错误并通过CaptureStartupErrors默认显示错误页面。在ASP.NET Core 2中,WebHost有一个static方法CreateDefaultBuilder,该方法会自动配置为使用Kestrel 侦听器(允许跨平台),加载配置,将上下文根设置为 Directory.GetCurrentDirectory
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
- Startup类改变了。将IAppBuilder替换为IApplicationBuilder。在Startup类现在有两种方法。第一种方法是void ConfigureServices(IServiceCollection services),它基本上是一个IOC(控制反转)容器,其中注入了所有依赖项。第二种方法是void Configure(IApplicationBuilder app),用于注册中间件。此外,Startup类还可以在Configure方法或Startup构造函数中采用可选IHostingEnvironment和/或可选ILoggerFactory参数。要使用Web API框架,首先需要通过在第一个方法中调用AddMvc来添加服务,然后可以通过在第二个方法中调用 UseMvc来使用该服务。此外,HttpConfiguration会被接受IRouteBuilder参数的委托替换。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder appBuilder,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory
.AddConsole();
appBuilder
.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
}
}
- 中间件类与本机OWIN中间件类具有相同的结构。然而,它使用了HttpContext而不是IDictionary<string, object>,而“next”组件是一种称为RequestDelegate而不是Func<IDictionary<string, object>, Task>委托。HttpContext不是旧的System.Web对象,而是一个新的轻量级的对象。可以使用Microsoft.AspNetCore.Owin包中的OwinEnvironment类将对象转换为OWIN环境对象。RequestDelegate相当于Func<HttpContext, Task>。这是新的中间件类的示例。
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
//Inbound processing
return this._next(context);
//Outbound processing
}
}
总体而言,ASP.NET Core应用了OWIN中列出的核心概念,即使它脱离了Katana实现。注:所有项目第4部分——托管OWIN Katana应用程序和第5部分——使用OWIN Katana开发Web API能在github上下载。