精华内容
下载资源
问答
  • 深入了解OWIN Katana
    2020-12-20 23:22:41

    目录

    介绍

    第1部分——什么是OWIN

    第2部分——什么是Katana

    第3部分——使用OWIN Katana进行应用程序开发

    3.1. 创建侦听器或服务器

    3.2. 配置中间件管道

    3.3. 创建中间件

    3.3.1. 创建中间件委托,由委托注册

    3.3.2. 创建一个中间件类,按类类型注册

    3.3.3. 创建中间件类,并通过类实例进行注册

    3.4. IIS集成管道

    第4部分——托管OWIN Katana应用程序

    4.1. 自托管为控制台应用程序

    4.2. 自托管为WinForms应用程序

    4.3. 自托管作为Windows服务

    4.4. IIS主机

    4.5. 用OwinHost托管

    第5部分——使用OWIN Katana开发Web API

    5.1. 具有自托管功能的Web API

    5.2. 带有IIS托管的Web API

    第6部分——从OWIN Katana到ASP.NET Core

    参考文献


    [注意:您可以将下载的项目用作模板来启动OWIN KatanaWebAPI应用程序。

    介绍

    我被要求向不同的开发团队进行有关OWINWeb API的介绍,因为他们想要对该技术进行介绍,而该团队中没有人以前接触过该技术。在此之前,我为公司开发了许多内部API。借助演示幻灯片和材料,我决定也将它们记录为博客文章,以便对其进行正确记录。与原始演示文稿相比,我投入了很多额外的信息,以覆盖更广泛的互联网受众。

    即使最近对ASP.NET Core进行了革命性的更改,我仍然发现,了解OWIN对于理解ASP.NET Core的框架和功能至关重要。OWIN中有许多概念和体系结构设计仍然适用于ASP.NET Core。这篇文章主要关注OWINKatana,后者是OWIN的实现。详细解释了OWIN Katana的内部工作。之后,将分配一小章来讨论使用OWIN Katana进行Web API开发,最后是对ASP.NET Core的简要介绍。

    1部分——什么是OWIN

    OWIN代表.NETOpen Web InterfaceOWIN是一个开放标准规范,它定义了.NET Web服务器和Web应用程序之间的标准接口。目的是提供一种简单、可插拔且轻便的标准接口。

    OWIN受到开发其他编码语言的Web框架的激励,例如JavaScriptNode.jsRubyRackPhytonWSGI。所有这些Web框架都设计为快速、简单并且它们以模块化方式支持Web应用程序的开发。相反,在OWIN之前,每个.NET Web应用程序都需要对> System.Web.dll的依赖,该依赖关系与MicrosoftIISInternet信息服务)紧密耦合。这意味着.NET Web应用程序附带了许多来自IIS的应用程序组件堆栈,无论是否实际需要它们。这使.NET Web应用程序总体上较重,并且在许多基准测试中,它们的运行速度均比其他编码语言中的同类应用程序慢。

    OWINMicrosoft社区的成员发起;例如C#、F#和动态编程社区。因此,规范在很大程度上受到那些社区的编程范例的影响。从最初的原型制作开始,OWIN被简化为一个通用规范。它基本上指定,如图1所示:

    1. Web服务器和Web应用程序之间以及Web应用程序组件之间要传递的对象的类型。这也称为环境对象。
    2. Web应用程序组件的结构。结构应该简单,并以一致的方式表示。

    除基础类语言(FCL)类型和支持异步处理的需求外,其他约束还包括没有依赖性。

    1. OWIN的范围

     

    2. OWIN规范(Web服务器和Web应用程序之间的交互)

    2显示了OWINWeb服务器和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。这也意味着委托支持异步调用。委托函数的名称为AppFuncOWIN中的Web应用程序组件称为中间件。

    3、管道模型用于构建中间件。不幸的是,OWIN 1.0没有指定中间件如何链接到管道中。后来针对OWIN 1.0的草案尝试包括中间件和管道构建器的规范。自201631日起,它是已过期的在建工程草案,似乎已被放弃。即使未指定,在实际实现中始终需要管道/中间件/应用程序构建器。

    那么构建器注册中间件的要求是什么呢?流水线结构暗示中间件的实现可以选择调用其他或下一个中间件。例如,第一个中间件将调用下一个中间件,依此类推,直到最后一个直接终止或返回调用的中间件(第一个中间件通常称为直通中间件,最后一个被称为终止中间件)。为了能够将中间件作为委托函数链接到管道(委托是OWIN中的一等公民,主要受函数式编程范例的影响),构建器需要具有Func<AppFunc, AppFunc>签名的委托。第一个参数(AppFunc即参数)是next中间件,而第二个中间件(即返回)是中间件主体。如果中间件是一个直通中间件,那么中间件体将调用next中间件。

    当构建器构建管道时,它以相反的顺序依次执行已注册的委托函数,并将最后一次执行的结果(中间件主体)作为下一次执行的参数(已执行的委托函数的参数'next'),直到检索到第一个中间件主体(AppFunc)并充当管道的入口点。对于函数式程序员(F#)来说,这是一个绝妙的解决方案,但是对于面向对象的程序员(C#),这可能会造成混乱,因为这种方法不是一种规范,并且与面向对象的思想不符,即使C#语言本身支持委托。稍后将在讨论使用Katana创建中间件时讨论此问题,KatanaC#中的OWIN实现。

    OWIN明确区分了主机、服务器、中间件、Web框架和Web应用程序。但是,OWIN规范仅处理主机、服务器和中间件的一部分。以下是从OWIN网站获取的说明。

    1. Server直接与客户端通信,然后使用OWIN语义处理请求的HTTP服务器。服务器可能需要转换为OWIN语义的适配器层。
    2. Web Framework— OWIN之上的独立组件,公开了自己的对象模型或API,应用程序可以使用它们来促进请求处理。Web框架可能需要从OWIN语义转换的适配器层。
    3. Web Application —使用OWIN兼容服务器运行的特定应用程序,可能建立在Web框架之上。
    4. Middleware —穿过组成服务器和应用程序之间管道的组件,以检查、路由或修改用于特定目的的请求和响应消息。
    5. 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#社区的KatanaF#社区的Freya。还有许多仅实现托管和服务器OWIN规范的库,例如NowinSuave。这些列表在OWIN网站上。

    KatanaMicrosoftOWIN规范的实现。它们采用nuget包的形式,它们都有一个命名空间Microsoft.Owin。但是,Katana不仅是OWIN的实现,还具有其他抽象来帮助提高开发效率。Katana添加了许多面向对象的构造,例如IOwinContext接口和OwinMiddleware抽象类。IOwinContext将被用作对环境对象的抽象和可通过采取IDictionary<string, object>对象作为构造函数参数来实例化为OwinContext,而OwinMiddleware用作创建中间件类的基类。实际上,OWIN尚未指定中间件或管道构建器的标准形式。但是,从OWIN中间件草案的外观来看,仅使用委托。相反,Katana作为OWIN的面向对象的C#实现,使用实现了接口IAppBuilderAppBuilder类作为管道构建器。除此之外,Katana还允许将中间件创建为类,并且AppBuilder可以使用类类型或类实例将其注册。应该假定这些Katana的中间件类只能被AppBuilder使用

    Katana还具有许多构造,以帮助访问环境对象,例如IOwinRequestIOwinResponse,分别由OwinRequestOwinResponse类实现。OWIN兼容的中间件仍然可以由有限的作者使用Katana的结构,如OwinContextOwinRequestOwinResponse中间件体。

    此外,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 ——OWINKatana实现
    • 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、活动目录、FacebookGooglejwtoauthwsfederationtwitterMicrosoft Account, open ID connect
    • Owin ——OWIN抽象
    • OwinHost ——Katana主机实现

    这些程序集通过一些nuget包来分发,例如,OwinHostMicrosoft.Owin.Host.SystemWebMicrosoft.Owin.Host.SelfHostKatana实现是开源的,可以从https://github.com/aspnet/AspNetKatana访问源代码。

    3部分——使用OWIN Katana进行应用程序开发

    如前所述,OWIN Katana提供了一种编写Web应用程序的结构。诸如日志记录和异常处理之类的横切关注点以及诸如Web API之类的Web框架都可以编写为中间件。可以使用在管道中注册的Web框架中间件来编写主要的Web应用程序逻辑。在这一部分中,OWIN Katana应用程序开发归因于编写中间件,创建中间件管道以及启动侦听器或服务器的活动。下一部分将单独讨论使用Web API框架开发Web应用程序。

    3.1. 创建侦听器或服务器

    仅在自托管方案中才需要创建侦听器或服务器,而OwinHostIIS托管模型则不需要。Katana提供了一个static方法,WebApp.StartMicrosfot.Owin.Hosting创建一个或多个服务器。创建服务器与创建侦听器并激活它们是相同的。有许多重载方法和通用方法可以做到这一点。

    1. IDisposable Start(string url, Action<IAppBuilder> startup)
    2. IDisposable Start(StartOptions startOptions, Action<IAppBuilder> startup)
    3. IDisposable Start<TStartup>(string url)
    4. IDisposable Start<TStartup>(StartOptions startOptions)
    5. IDisposable Start(StartOptions startOptions)
    6. IDisposable Start(string url)

    OWIN Katana中创建侦听器时,需要两个重要的信息,即侦听urlstartup 委托,如第一个重载所示。侦听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

    使用第二种重载方法,可以通过设置StartOptionsUrls属性来创建多个监听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类或委托可以在StartupOptionAppStartup属性中设定。该属性可以采用'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类的规则:

    1. Naming convention——Katana将查找static或带有名称Startup(区分大小写)的实例类,并在根(全局)命名空间或与程序集名称匹配的命名空间中包含名称为Configurationstatic或实例方法。
    2. OwinStartupAttribute——此方法优先于命名约定。Katana将查找指定为assembly 级别的属性,例如[Assembly:OwinStartup(typeof(CustomStartup))]。可以在允许assembly属性的任何地方声明它。该属性具有三种重载方法。第一个与示例类似,采用startup类的类型。第二个则采用startup类的类型及其startup方法。第三个是一个友好的名称和一个startup类的类型。友好名称将与配置文件结合使用。
    3. Configuration file——该方法优先于OwinStartupAttribute方法。Katana将在应用程序配置文件中查找具有该appSettings部分下方owin:appStartup名称的键。通过指定类的全名,可以使用该键直接链接到startup类。或者,可以通过指定属性的友好名称来使用该键链接到OwinStartupAttribute。可以声明多个OwinStartupAttribute,每个都有不同的友好名称。然后,配置文件可以决定在owin:appStartup键中使用哪一个。

    该应用程序还可以使用另一个配置键owin:AutomaticAppStartup(将其设置为truefalse)来显式控制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提供了许多扩展方法。一些重要的是:

    1. Use<T>(string[] args)
    2. Use(Func<IOwinContext, Func<Task> /*next*/, Task> handler)
    3. Run(Func<IOwinContext, Task> handler)
    4. Map(string pathMatch, Action<IAppBuilder> configuration)
    5. MapWhen(Predicate predicate, Action<IAppBuilder> configuration)
    6. 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));    
    }

    MapWhenMapWhenAsync方法类似于Map方法,不同之处在于使用其评估的谓词(predicate)委托,即返回一个布尔表达式,而不是使用路径匹配。该MapWhenAsync特别使用awaitable谓词(predicate)委托。在钩子之下,他们使用MapWhenMiddleware中间件。如果谓词(predicate)委托返回true,则中间件执行映射管道,否则它将继续执行到主管道中的下一个中间件。谓词(predicate)子句带有一个IOwinContext参数,它使您可以灵活地评估整个请求,而不仅仅是请求的URI Path。以下是MapWhenMapWhenAsync方法用法的示例。

    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));
    }

    当在管道中使用多个MapMapWhen时,应首先放置更具体的约束,否则它们将永远没有机会被评估。

    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>。例如,如果中间件具有stringint参数,则委托必须为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方法中使用。首先需要通过委托构造或转换(23)将它们转换为委托。方法4显示了使用中间件选项注册中间件委托。委托或中间件方法可以分为两个执行块。构建管道后,第一个块将执行一次。对于每个收到的http请求,将执行第二个块或中间件主体。

    正如上一节中所讨论的,提供Katana扩展方法UseRun,其可以分别接受的中间件处理程序委托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方法(12)和不使用Initialize方法(34)的中间件类。方法24演示了带有中间件参数的中间件类。对于方法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,例如LoggingExceptionHandling等)。

    3.4. IIS集成管道

    如前所述,Katana不仅是OWIN实现。尽管OWIN的最初目的是从IIS中删除应用程序依赖项,但Katana提供了一种使用IIS托管Web应用程序的机制。前面的所有示例都是根据自托管的上下文编写的。那么,如果OWIN Katana应用程序是由IIS实现并托管的,那会有什么不同呢?至少存在一些差异:

    • 使用IIS托管时,侦听器和服务器应用程序由IIS基础结构创建和维护。因此,无需创建侦听器。但是,要允许侦听器将http请求转换为OWIN语义并自动检测OWIN Startup类,则OWINMicrosoft.Owin.Host.SystemWeb必须位于应用程序bin文件夹中。这可以通过引用nugetMicrosoft.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管道结合使用,排序如下:

    1. Authenticate阶段:MiddlwareOneIIS功能
    2. PostAuthenticate 阶段:IIS功能
    3. Authorize 阶段:IIS功能
    4. PostAuthorize 阶段:IIS功能
    5. ResolveCache 阶段:IIS功能
    6. PostResolveCache 阶段:IIS功能
    7. MapHandler 阶段:IIS功能
    8. PostMapHandler阶段:MiddlewareTwoIIS功能
    9. AcquireState 阶段:IIS功能
    10. PostAcquireState 阶段:IIS功能
    11. PreHandlerExecute阶段:MiddlewareThree MiddlewareFour
    12. ExecuteRequestHandler 阶段:仅IIS功能(此处没有OWIN中间件)

    IIS功能代表可以通过IISWeb配置进行配置的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 FrameworkOWIN Katana依赖于.NET Framework,因此,本部分中的所有项目都针对.NET Framework

    4.1. 自托管为控制台应用程序

    作为控制台应用程序自托管,侦听器或服务器可以在程序启动时或由命令行交互触发时启动。下面的项目演示了前一个。

    1、创建一个控制台应用程序(.NET Framework项目,并命名为Owin.SelfHosting.Console。如果使用其他版本的Visual Studio,请使用等效的项目模板。

    2、添加对nugetMicrosoft.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、重命名Form1Main,并将表单的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到步骤,但将命名空间更改为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,以显示设计器表单,其中将包含两个组件:serviceProcessIntaller1serviceInstaller1

    7、将serviceProcessInstaller1Account属性设置为LocalSystem'

    8、确保serviceInstaller1ServiceName设置为'OwinService。可选的,serviceInstaller1DisplayNameStartType可以为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、添加对nugetMicrosoft.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'来运行。

    1. 4.4 IIS托管步骤1到步骤3开始执行,但将命名空间更改为Owin.OwinHosting
    2. 安装nuget软件包OwinHost3.1.0),这将添加对该软件包的引用,并在'WebProjectProperties'项目文件(.csproj)中添加'Servers'条目。此必不可少的功能将添加'OwinHost'为自定义托管。
    3. 在设计视图中打开项目属性,在'Web'选项卡中,在'Server'部分,检查'apply server settings to all users (store in project file)',从下拉列表框中选择'OwinHost',并在'Project Url'中输入'http://localhost:9000/owinhosting'
    4. Visual Studio运行该应用程序,然后'OwinHost'应当启动并托管该应用程序。
    5. Visual Studio还将启动浏览器并指向'http://localhost:9000/owinhosting',它应该显示'I am Peter. Welcome to this site'

    当使用OwinHost托管时,可以使用nuget软件包Microsoft.Owin.SelfHost而不是使用软件包Microsoft.Owin.Host.SystemWebOwinHost托管可以与在两个侦听器Microsoft.Owin.Host.SystemWebMicrosoft.Owin.Host.HttpListener(这是在Microsoft.Owin.SelfHost包中)一起工作。

    5部分——使用OWIN Katana开发Web API

    尽管可以使用OWIN Katana开发RESTful API,但它没有提供足够的杠杆作用或抽象来有效地做到这一点。用OWIN Katana编写中间件最适合Web框架或Web应用程序的交叉需求。开发RESTful API时,最好使用本部分将使用的Web框架,例如NancyMicrosoft Web API

    Web API可以用于ASP.NET应用程序和OWIN Katana应用程序。使用OWIN Katana进行开发的好处是可以灵活地从各种托管方案中进行选择,如上一部分所述。ASP.NET(最高4.6)应用程序只能由IIS托管。两者之间的另一个主要区别是注册Web API中间件的入口点。对于OWIN Katana应用程序,它是在startup方法或Startup类中注册的,而对于ASP.NET应用程序,它是在Global.asaxApplication_Startup方法中注册的。但是,后来称为ASP.NET CoreASP.NET 5,应用程序已经在使用Startup类,其遵循OWIN Katana' style'

    使用OWIN Katana实现Web API时,Web API只是管道中的中间件,可以使用一个带有HttpConfiguration参数的UseWebApi方法进行注册。Web API提供了开发API时可以使用的高级构造,例如:控制器、路由器、格式化程序、查询解析模型绑定,并且大多数可以通过HttpConfiguration配置。

    以下链接显示了Web APIWeb API 2.0)内部的管道结构。WebAPI的第一阶段HttpMesageHandlers包含三种类型的处理程序。类似于中间件的Delegating处理程序可以通过HttpConfiguration注入。HttpRoutingDispatcher处理程序支持管道分支,可以通过HttpConfiguration被配置。最后一个处理程序HttpControllerDispatcher将请求路由到正确的控制器。Web API控制器必须从ApiController类派生。

    WebAPI的第二阶段是控制器阶段。在执行控制器方法之前,需要进行很多处理。如果存在则执行AuthenticationAuthorization过滤器。接下来,模型绑定尝试将请求(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.OwinSelfHost3.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.Owin5.2.3)和Microsoft.Owin.SelfHost3.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 APIMapHttpAttributeRoutes表示基于控制器上的路由属性的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属性标记类,并使用RouteHttpGet属性标记方法。该调用的路由为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

    1. 创建一个ASP.NET Web Application (.NET Framework)项目,选择一个Empty模板。如果使用其他版本的Visual Studio,请使用等效的项目模板。
    2. 有两个要安装的nuget软件包。首先,添加Microsoft.Owin.Host.SystemWeb程序包,这将添加以下引用:
      • Microsoft.Owin
      • Microsoft.Owin.Host.SystemWeb
      • Owin
    3. 其次,添加Microsoft.AspNet.WebApi.Owin程序包,这将添加以下引用:
      • Microsoft.AspNet.WebApi.Client
      • Microsoft.AspNet.WebApi.Core
      • Microsoft.AspNet.WebApi.Owin
      • Newtonsoft.Json
    4. 5.2. 具有自托管功能的Web API执行步骤3和步骤,但将命名空间更改为Owin.WebApi.IISHosting
    5. 在设计视图中打开项目属性,在'Web'标签中,在'Server'部分,检查'apply server settings to all users (store in project file)',从下拉列表框中选择'IIS Express',并在'Project Url'中输入'http://localhost:9000/webapi/iishosting'
    6. 要测试该应用程序,请运行所有浏览器并输入'http://localhost:9000/webapi/iishosting/user/1234'
    7. 浏览器应显示一个包含用户详细信息的json响应。

    6部分——从OWIN KatanaASP.NET Core

    近年来,.NET Web开发技术发生了许多变化。与其他语言的Web开发保持一致,趋势是开发更小、更集中的组件,这些组件会更频繁地发布。

    OWIN是朝着更加模块化、轻量级、开放平台和开源Microsoft Web产品迈进的架构设计的开始和基础。KatanaMicrosoft尝试提供开发人员可以体验的实现。来自Katana实现和用法的反馈被反馈到下一个Microsoft Web产品ASP.NET 5的开发中。

    ASP.NET 52015年首次发布。它将来自Web APIMVC 5的最佳功能组合到一个称为MVC6的产品中。同时,微软还推出了一个名为.NET Core 5的新框架,该框架还针对LinuxMacOS等其他平台。.NET Core Framework是精简版.NET Framework,并作为一组nuget软件包进行分发。ASP.NET 5可以在.NET Core 5.NET Framework上运行。

    20161月开始,ASP.NET 5名称更改为ASP.NET Core,此举与以前的仅针对.NET FrameworkMicrosoft ASP.NET 4.6有所不同。目前,ASP.NET Core 2产品于20178月与.NET Core 2 Framework一起发布,并在Visual Studio 2017更新版本3中受支持。但是.NET Core 2 Framework需要单独下载。

    ASP.NET Core本质上支持OWIN,但不支持依赖于.NET FrameworkKatana实现。尽管ASP.NET CoreOWINKatana中遵循相同的概念,也具有其自己的抽象。例如,它使用IApplicationBuilder而不是IAppBuilderHttpContext而不是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 KatanaWeb API实现也有许多变化和偏差,例如:

    • 使用静态方法WebApp.Start启动服务器或监听器改变以使用IWebHostRun()Start()。它必须 在运行对象之前先建立WebHostBuilder对象。UseUrlsUseStartup<T>用来指定监听地址和各自的Startup类。该WebHostBuilder也有很多其他的方法。例如,构建器可以指定托管模式,比如通过IIS集成UseIISIntegration,通过UseConfiguration配置,通过UseServer服务器类型通过UseEnvironment环境,通过UseContentRootUseWebRoot上下文根或网站根目录或捕获错误并通过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下载。

    参考文献

    1. OWIN website
    2. Microsoft ASP.NET Web API (2.0) Poster
    3. OWIN Startup class detection
    4. Using OWIN pipeline in ASP.NET Core
    5. Katana, ASP.NET 5, and bridging the gap
    6. Katana source files
    更多相关内容
  • owin-https-ssl

    2021-02-20 03:01:59
    owin-https-ssl
  • Microsoft.Owin_C#_Owin_

    2021-10-01 17:38:24
    Microsoft.Owin is a library for authentication and authorization
  • 换句话说,如果你的项目使用OWIN实现OAuth 2.0 之客户端模式,但是没有配置machineKey,也没有在认证服务中自定义认证。那么我就可以利用OWIN实现OAuth 2.0 之客户端模式的基本原理生成一个token,你会发现我这个...
  • 使用 OWIN 实现 Razor 模板 安装 Install-Package Mezm.Owin.Razor 使用 using Mezm . Owin . Razor ; using Mezm . Owin . Razor . Routing ; public class Startup { public void Configuration ( IAppBuilder ...
  • c# WebApi Owin 适用Microsoft.Owin.Security.OAuth Authorize - 特性验证
  • 本例子采用两种方式做实现,一个是console控制程序自身采用Owin方式将Odata服务自宿主在自身。另一种方式是Console控制体程序采用Owin方式加载webApi网站dll方式。同时控制台程序实现了windows服务的注册和删除功能...
  • 基于OWIN的Asp.Net Web Api的自宿主项目模板,可打包成Windows服务,简单使用了TopSHelf
  • OpenIddict旨在提供一种通用解决方案,以在任何ASP.NET Core 2.1、3.1和5.0应用程序中实现OpenID Connect服务器和令牌验证,并且从OpenIddict 3.0开始,任何也使用Microsoft.Owin的ASP.NET 4.x应用程序。...
  • 解决方法: [HttpGet] public ... { cltime time = new cltime(); time.datetime = DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss); string relsut = JsonConvert.SerializeObject(time);...
  • Barebone.Router - 用于 .NET/Mono 的 OWIN 路由器 using OwinEnv = IDictionary ; public class SimpleExample { public Task ProcessRequest ( OwinEnv env ){ var router = new Router (); var route = new...
  • Microsoft.Owin.Compression 移除之前, //katanaproject.codeplex.com/压缩模块的前叉。 我一直在使用Katana Project中的这个方便的子组件,然后再对其进行维护和最终删除。 我在这里对其进行了分叉,并修复了一...
  • asp.net WebAPI OWIN OAuth2.0授权自定义返回结果及错误或异常问题处理核心代码,详情: https://www.cnblogs.com/wgx0428/p/12315546.html
  • 一个OWIN主机,可以承载一个或多个OWIN应用程序以允许模拟反向代理。 意向能够在负载平衡(循环)和分区方案中接受测试应用程序的行为。 通常是星期天下午的骇客; 尚未作为nuget软件包提供。 如果我或其他人觉得...
  • Owin实现OAuth2.0 授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)
  • 基于Owin中间件的OAuth2.0身份认证,文章位置https://blog.csdn.net/u013938578/article/details/82956188
  • 欧文·南希·杭菲尔(OwinNancyHangfire) 使用Owin通过Hangfire自托管的NancyFx 参考链接: 变更记录: 添加了对Nancy bootstrapper的基本Castle.Windsor IoC支持以在模块之间使用
  • 它应该使用ASP.NET身份,是一个自托管的OWIN WebApi(不依赖IIS),并且可以通过设置名为COMPILE_AS_A_SERVICE的条件编译符号来轻松地转换为Windows Service。 而且,基础数据库应该易于互换并且不能“钉”在MS SQL...
  • #Owin 托管选项# 此 Visual Studio 解决方案演示了在控制台应用程序、IIS 和内存中(用于集成测试)中托管基于 OWIN 的 WebApi - WebApi 本身位于单独的自包含类库中。 它有以下项目: Api:具有单个控制器...
  • 基于Microsoft.Owin.Security.OAuth实现OAuth 2.0所有应用场景,可集成单点登录功能
  • VS2015创建Web应用程序自动生成OWIN+AspNet.Identity 的EntityFramework实现,只要配置好数据库连接就可以了,本示例用NHibernate代替EntityFramework的实现,帮助理解OWIN+AspNet.Identity的内部实现过程
  • OWIN host WebAPI2 使用独立程序集中的Controller的Demo程序
  • 欧文·斯威格(OwinSwagger) 使用Swashbuckle记录自托管OWIN Web API的快速入门示例
  • 在开发一个ASP.NET Web API项目写单元测试时,实在无法忍受之前的笨方法,决定改过自新。  之前Web API的单元测试需要进行以下的操作:  初始配置:  1)在IIS中创建一个站点指定Web API项目 ...
  • 用于.NET Framework 4.6.1 +,Mono和DotNET Core 3+的OWIN服务器和Web API库。 为什么? 这个项目之所以成立,是因为Microsoft的OWIN库仅针对.NET Framework,并且我需要同时针对.NET Framework和DotNET Core的OWIN...
  • Owin

    2018-08-14 09:38:00
    下面我们使用 ASP.NET WebApi OWIN,分别实现上面的四种授权模式。 2. 授权码模式(authorization code) 简单解释:落网提供一些授权凭证,从微博授权服务获取到 authorization_code,然后根据 authorization...

    OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

    OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。


    以上概念来自:https://zh.wikipedia.org/wiki/OAuth

    OAuth 是什么?为什么要使用 OAuth?上面的概念已经很明确了,这里就不详细说明了。

    阅读目录:

    • 运行流程和授权模式
    • 授权码模式(authorization code)
    • 简化模式(implicit grant type)
    • 密码模式(resource owner password credentials)
    • 客户端模式(Client Credentials Grant)

    开源地址:https://github.com/yuezhongxin/OAuth2.Demo

    1. 运行流程和授权模式

    关于 OAuth 2.0 的运行流程(来自 RFC 6749):

    这里我们模拟一个场景:用户听落网,但需要登录才能收藏期刊,然后用快捷登录方式,使用微博的账号和密码登录后,落网就可以访问到微博的账号信息等,并且在落网也已登录,最后用户就可以收藏期刊了。

    结合上面的场景,详细说下 OAuth 2.0 的运行流程:

    • (A) 用户登录落网,落网询求用户的登录授权(真实操作是用户在落网登录)。
    • (B) 用户同意登录授权(真实操作是用户打开了快捷登录,用户输入了微博的账号和密码)。
    • (C) 由落网跳转到微博的授权页面,并请求授权(微博账号和密码在这里需要)。
    • (D) 微博验证用户输入的账号和密码,如果成功,则将 access_token 返回给落网。
    • (E) 落网拿到返回的 access_token,请求微博。
    • (F) 微博验证落网提供的 access_token,如果成功,则将微博的账户信息返回给落网。

    图中的名词解释:

    • Client -> 落网
    • Resource Owner -> 用户
    • Authorization Server -> 微博授权服务
    • Resource Server -> 微博资源服务

    其实,我不是很理解 ABC 操作,我觉得 ABC 可以合成一个 C:落网打开微博的授权页面,用户输入微博的账号和密码,请求验证。

    OAuth 2.0 四种授权模式:

    • 授权码模式(authorization code)
    • 简化模式(implicit)
    • 密码模式(resource owner password credentials)
    • 客户端模式(client credentials)

    下面我们使用 ASP.NET WebApi OWIN,分别实现上面的四种授权模式。

    2. 授权码模式(authorization code)

    简单解释:落网提供一些授权凭证,从微博授权服务获取到 authorization_code,然后根据 authorization_code,再获取到 access_token,落网需要请求微博授权服务两次。

    第一次请求授权服务(获取 authorization_code),需要的参数:

    • grant_type:必选,授权模式,值为 "authorization_code"。
    • response_type:必选,授权类型,值固定为 "code"。
    • client_id:必选,客户端 ID。
    • redirect_uri:必选,重定向 URI,URL 中会包含 authorization_code。
    • scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
    • state:可选,客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值,比如微博授权服务值为 weibo。

    第二次请求授权服务(获取 access_token),需要的参数:

    • grant_type:必选,授权模式,值为 "authorization_code"。
    • code:必选,授权码,值为上面请求返回的 authorization_code。
    • redirect_uri:必选,重定向 URI,必须和上面请求的 redirect_uri 值一样。
    • client_id:必选,客户端 ID。

    第二次请求授权服务(获取 access_token),返回的参数:

    • access_token:访问令牌.
    • token_type:令牌类型,值一般为 "bearer"。
    • expires_in:过期时间,单位为秒。
    • refresh_token:更新令牌,用来获取下一次的访问令牌。
    • scope:权限范围。

    ASP.NET WebApi OWIN 需要安装的程序包:

    • Owin
    • Microsoft.Owin.Host.SystemWeb
    • Microsoft.Owin.Security.OAuth
    • Microsoft.Owin.Security.Cookies
    • Microsoft.AspNet.Identity.Owin

    在项目中创建 Startup.cs 文件,添加如下代码:

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, AuthenticationMode = AuthenticationMode.Active, TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址 AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址 AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间 Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务 AuthorizationCodeProvider = new OpenAuthorizationCodeProvider(), //authorization_code 授权服务 RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务 }; app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式 } }

    OpenAuthorizationServerProvider 示例代码:

    public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        /// <summary> /// 验证 client 信息 /// </summary> public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId; string clientSecret; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (clientId != "xishuai") { context.SetError("invalid_client", "client is not valid"); return; } context.Validated(); } /// <summary> /// 生成 authorization_code(authorization code 授权方式)、生成 access_token (implicit 授权模式) /// </summary> public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context) { if (context.AuthorizeRequest.IsImplicitGrantType) { //implicit 授权方式 var identity = new ClaimsIdentity("Bearer"); context.OwinContext.Authentication.SignIn(identity); context.RequestCompleted(); } else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType) { //authorization code 授权方式 var redirectUri = context.Request.Query["redirect_uri"]; var clientId = context.Request.Query["client_id"]; var identity = new ClaimsIdentity(new GenericIdentity( clientId, OAuthDefaults.AuthenticationType)); var authorizeCodeContext = new AuthenticationTokenCreateContext( context.OwinContext, context.Options.AuthorizationCodeFormat, new AuthenticationTicket( identity, new AuthenticationProperties(new Dictionary<string, string> { {"client_id", clientId}, {"redirect_uri", redirectUri} }) { IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan) })); await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext); context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token)); context.RequestCompleted(); } } /// <summary> /// 验证 authorization_code 的请求 /// </summary> public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context) { if (context.AuthorizeRequest.ClientId == "xishuai" && (context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType)) { context.Validated(); } else { context.Rejected(); } } /// <summary> /// 验证 redirect_uri /// </summary> public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { context.Validated(context.RedirectUri); } /// <summary> /// 验证 access_token 的请求 /// </summary> public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context) { if (context.TokenRequest.IsAuthorizationCodeGrantType || context.TokenRequest.IsRefreshTokenGrantType) { context.Validated(); } else { context.Rejected(); } } }

    需要注意的是,ValidateClientAuthentication 并不需要对 clientSecret 进行验证,另外,AuthorizeEndpoint 只是生成 authorization_code,并没有生成 access_token,生成操作在 OpenAuthorizationCodeProvider 中的 Receive 方法。

    OpenAuthorizationCodeProvider 示例代码:

    public class OpenAuthorizationCodeProvider : AuthenticationTokenProvider
    {
        private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal); /// <summary> /// 生成 authorization_code /// </summary> public override void Create(AuthenticationTokenCreateContext context) { context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); _authenticationCodes[context.Token] = context.SerializeTicket(); } /// <summary> /// 由 authorization_code 解析成 access_token /// </summary> public override void Receive(AuthenticationTokenReceiveContext context) { string value; if (_authenticationCodes.TryRemove(context.Token, out value)) { context.DeserializeTicket(value); } } }

    上面 Create 方法是 await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext); 的重载方法。

    OpenRefreshTokenProvider 示例代码:

    public class OpenRefreshTokenProvider : AuthenticationTokenProvider
    {
        private static ConcurrentDictionary<string, string> _refreshTokens = new ConcurrentDictionary<string, string>(); /// <summary> /// 生成 refresh_token /// </summary> public override void Create(AuthenticationTokenCreateContext context) { context.Ticket.Properties.IssuedUtc = DateTime.UtcNow; context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(60); context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); _refreshTokens[context.Token] = context.SerializeTicket(); } /// <summary> /// 由 refresh_token 解析成 access_token /// </summary> public override void Receive(AuthenticationTokenReceiveContext context) { string value; if (_refreshTokens.TryRemove(context.Token, out value)) { context.DeserializeTicket(value); } } }

    refresh_token 的作用就是,在 access_token 过期的时候,不需要再通过一些凭证申请 access_token,而是直接通过 refresh_token 就可以重新申请 access_token。

    另外,需要一个 api 来接受 authorization_code(来自 redirect_uri 的回调跳转),实现代码如下:

    public class CodesController : ApiController
    {
        [HttpGet]
        [Route("api/authorization_code")] public HttpResponseMessage Get(string code) { return new HttpResponseMessage() { Content = new StringContent(code, Encoding.UTF8, "text/plain") }; } }

    基本上面代码已经实现了,单元测试代码如下:

    public class OAuthClientTest
    {
        private const string HOST_ADDRESS = "http://localhost:8001"; private IDisposable _webApp; private static HttpClient _httpClient; public OAuthClientTest() { _webApp = WebApp.Start<Startup>(HOST_ADDRESS); Console.WriteLine("Web API started!"); _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri(HOST_ADDRESS); Console.WriteLine("HttpClient started!"); } private static async Task<TokenResponse> GetToken(string grantType, string refreshToken = null, string userName = null, string password = null, string authorizationCode = null) { var clientId = "xishuai"; var clientSecret = "123"; var parameters = new Dictionary<string, string>(); parameters.Add("grant_type", grantType); if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) { parameters.Add("username", userName); parameters.Add("password", password); } if (!string.IsNullOrEmpty(authorizationCode)) { parameters.Add("code", authorizationCode); parameters.Add("redirect_uri", "http://localhost:8001/api/authorization_code"); //和获取 authorization_code 的 redirect_uri 必须一致,不然会报错 } if (!string.IsNullOrEmpty(refreshToken)) { parameters.Add("refresh_token", refreshToken); } _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret))); var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)); var responseValue = await response.Content.ReadAsStringAsync(); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); return null; } return await response.Content.ReadAsAsync<TokenResponse>(); } private static async Task<string> GetAuthorizationCode() { var clientId = "xishuai"; var response = await _httpClient.GetAsync($"/authorize?grant_type=authorization_code&response_type=code&client_id={clientId}&redirect_uri={HttpUtility.UrlEncode("http://localhost:8001/api/authorization_code")}"); var authorizationCode = await response.Content.ReadAsStringAsync(); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); return null; } return authorizationCode; } [Fact] public async Task OAuth_AuthorizationCode_Test() { var authorizationCode = GetAuthorizationCode().Result; //获取 authorization_code var tokenResponse = GetToken("authorization_code", null, null, null, authorizationCode).Result; //根据 authorization_code 获取 access_token _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); var response = await _httpClient.GetAsync($"/api/values"); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); } Console.WriteLine(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Thread.Sleep(10000); var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; //根据 refresh_token 获取 access_token _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken); var responseTwo = await _httpClient.GetAsync($"/api/values"); Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode); } }

    Startup 配置的 access_token 过期时间是 10s,线程休眠 10s,是为了测试 refresh_token。

    上面单元测试代码,执行成功,当然也可以用 Postman 模拟请求测试。

    3. 简化模式(implicit grant type)

    简单解释:授权码模式的简化版,省略 authorization_code,并且 access_token 以 URL 参数返回(比如 #token=xxxx)。

    请求授权服务(只有一次),需要的参数:

    • response_type:必选,授权类型,值固定为 "token"。
    • client_id:必选,客户端 ID。
    • redirect_uri:必选,重定向 URI,URL 中会包含 access_token。
    • scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。
    • state:可选,客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值,比如微博授权服务值为 weibo。

    需要注意的是,简化模式请求参数并不需要 grant_type,并且可以用 http get 直接请求。

    Startup 代码:

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, AuthenticationMode = AuthenticationMode.Active, TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址 AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址 AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间 Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务 RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务 }; app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式 } }

    OpenRefreshTokenProvider、OpenAuthorizationServerProvider 的代码就不贴了,和上面授权码模式一样,只不过在 OpenAuthorizationServerProvider 的 AuthorizeEndpoint 方法中有 IsImplicitGrantType 判断,示例代码:

    var identity = new ClaimsIdentity("Bearer");
    context.OwinContext.Authentication.SignIn(identity);
    context.RequestCompleted();

    这段代码执行会直接回调 redirect_uri,并附上 access_token,接受示例代码:

    [HttpGet]
    [Route("api/access_token")]
    public HttpResponseMessage GetToken() { var url = Request.RequestUri; return new HttpResponseMessage() { Content = new StringContent("", Encoding.UTF8, "text/plain") }; }

    单元测试代码:

    [Fact]
    public async Task OAuth_Implicit_Test() { var clientId = "xishuai"; var tokenResponse = await _httpClient.GetAsync($"/authorize?response_type=token&client_id={clientId}&redirect_uri={HttpUtility.UrlEncode("http://localhost:8001/api/access_token")}"); //redirect_uri: http://localhost:8001/api/access_token#access_token=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAfoPB4HZ0PUe-X6h0UUs2q42&token_type=bearer&expires_in=10 var accessToken = "";//get form redirect_uri _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await _httpClient.GetAsync($"/api/values"); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); } Console.WriteLine(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); }

    回调 redirect_uri 中的 access_token 参数值,因为在 URL 的 # 后,后端不好获取到,所以这里的单元测试只是示例,并不能执行成功,建议使用 Poastman 进行测试。

    4. 密码模式(resource owner password credentials)

    简单解释:在一开始叙述的 OAuth 授权流程的时候,其实就是密码模式,落网发起授权请求,用户在微博的授权页面填写账号和密码,验证成功则返回 access_token,所以,在此过程中,用户填写的账号和密码,和落网没有半毛钱关系,不会存在账户信息被第三方窃取问题。

    请求授权服务(只有一次),需要的参数:

    • grant_type:必选,授权模式,值固定为 "password"。
    • username:必选,用户名。
    • password:必选,用户密码。
    • scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。

    Startup 代码:

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, AuthenticationMode = AuthenticationMode.Active, TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址 AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址 AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间 Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务 RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务 }; app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式 } }

    OpenAuthorizationServerProvider 示例代码:

    public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        /// <summary> /// 验证 client 信息 /// </summary> public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId; string clientSecret; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (clientId != "xishuai") { context.SetError("invalid_client", "client is not valid"); return; } context.Validated(); } /// <summary> /// 生成 access_token(resource owner password credentials 授权方式) /// </summary> public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (string.IsNullOrEmpty(context.UserName)) { context.SetError("invalid_username", "username is not valid"); return; } if (string.IsNullOrEmpty(context.Password)) { context.SetError("invalid_password", "password is not valid"); return; } if (context.UserName != "xishuai" || context.Password != "123") { context.SetError("invalid_identity", "username or password is not valid"); return; } var OAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); OAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); context.Validated(OAuthIdentity); } }

    GrantResourceOwnerCredentials 内部可以调用外部服务,以进行对用户账户信息的验证。

    单元测试代码:

    [Fact]
    public async Task OAuth_Password_Test() { var tokenResponse = GetToken("password", null, "xishuai", "123").Result; //获取 access_token _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); var response = await _httpClient.GetAsync($"/api/values"); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); } Console.WriteLine(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Thread.Sleep(10000); var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken); var responseTwo = await _httpClient.GetAsync($"/api/values"); Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode); }

    5. 客户端模式(Client Credentials Grant)

    简单解释:顾名思义,客户端模式就是客户端直接向授权服务发起请求,和用户没什么关系,也就是说落网直接向微博提交授权请求,此类的请求不包含用户信息,一般用作应用程序直接的交互等。

    请求授权服务(只有一次),需要的参数:

    • grant_type:必选,授权模式,值固定为 "client_credentials"。
    • client_id:必选,客户端 ID。
    • client_secret:必选,客户端密码。
    • scope:可选,申请的权限范围,比如微博授权服务值为 follow_app_official_microblog。

    Startup 代码:

    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { AllowInsecureHttp = true, AuthenticationMode = AuthenticationMode.Active, TokenEndpointPath = new PathString("/token"), //获取 access_token 授权服务请求地址 AuthorizeEndpointPath=new PathString("/authorize"), //获取 authorization_code 授权服务请求地址 AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10), //access_token 过期时间 Provider = new OpenAuthorizationServerProvider(), //access_token 相关授权服务 RefreshTokenProvider = new OpenRefreshTokenProvider() //refresh_token 授权服务 }; app.UseOAuthBearerTokens(OAuthOptions); //表示 token_type 使用 bearer 方式 } }

    OpenAuthorizationServerProvider 示例代码:

    public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        /// <summary> /// 验证 client 信息 /// </summary> public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId; string clientSecret; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (clientId != "xishuai" || clientSecret != "123") { context.SetError("invalid_client", "client or clientSecret is not valid"); return; } context.Validated(); } /// <summary> /// 生成 access_token(client credentials 授权方式) /// </summary> public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { var identity = new ClaimsIdentity(new GenericIdentity( context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x))); context.Validated(identity); } }

    和其他授权模式不同,客户端授权模式需要对 client_secret 进行验证(ValidateClientAuthentication)。

    单元测试代码:

    [Fact]
    public async Task OAuth_ClientCredentials_Test() { var tokenResponse = GetToken("client_credentials").Result; //获取 access_token _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); var response = await _httpClient.GetAsync($"/api/values"); if (response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(response.StatusCode); Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage); } Console.WriteLine(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Thread.Sleep(10000); var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken); var responseTwo = await _httpClient.GetAsync($"/api/values"); Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode); }

    除了上面四种授权模式之外,还有一种就是更新令牌(refresh token),单元测试代码中已经体现了,需要额外的两个参数:

    • grant_type:必选,授权模式,值固定为 "refresh_token"。
    • refresh_token:必选,授权返回的 refresh_token。

    最后,总结下四种授权模式的应用场景:

    • 授权码模式(authorization code):引入 authorization_code,可以增加系统的安全性,和客户端应用场景差不多,但一般用于 Server 端。
    • 简化模式(implicit):无需 Server 端的介入,前端可以直接完成,一般用于前端操作。
    • 密码模式(resource owner password credentials):和用户账户相关,一般用于第三方登录。
    • 客户端模式(client credentials):和用户无关,一般用于应用程序和 api 之间的交互场景,比如落网开放出 api,供第三方开发者进行调用数据等。

    原文:https://www.cnblogs.com/xishuai/p/aspnet-webapi-owin-oauth2.html

    转载于:https://www.cnblogs.com/chenyishi/p/9472692.html

    展开全文
  • BioID.Owin.OAuth 用于 OWIN 的 BioID OAuth 2.0 客户端中间件。 安装 在您可以使用这个客户端软件之前,您必须登录 BioID(例如在 ),导航到并创建一个新的 BioID 客户端 ID 以获取您的唯一 ID 和秘密。 通过 ...
  • 使用在.NET中实现node.js。 版本根据递增。 这是Tomasz Janczuk的分支; 感谢他开始这个事情! 介绍 OWIN本身不是一项技术,而只是将Web应用程序与Web服务器分离的... var owin = require ( 'connect-owin' ) , ex
  • OWIN中间件提供了一种基于正则表达式规则动态修改传入URL请求的方法。 这允许您基于apache 以任意方式将任意URL映射到内部URL结构上 使用 请参阅示例项目: private class StartUp { public void Configuration...
  • OWIN OAuth 2.0 Authorization Server 用微软的 OWIN 插件,搭建了一个以 MVC5 为框架的 OAuth 2.0 Authorization Server。除了隐式授权,其他授权模式都对接了,AccessToken 正常,RefreshToken 正常,调用被保护...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,629
精华内容 1,851
关键字:

owin

友情链接: Errorbar_Plot.zip