zoukankan      html  css  js  c++  java
  • 为IIS Host ASP.NET Web Api添加Owin Middleware

    将OWIN App部署在IIS上                                           

    要想将Owin App部署在IIS上,只添加Package:Microsoft.OWIN.Host.SystemWeb包即可。它提供了所有Owin配置,Middleware注册等方面的Api.我们需要做的其实和SelfHost差不多。

    • 我们依然需要实现Startup类,但是不是通过WebApp来启动了。我们需要通过将Startup类打上[assembly: OwinStartup(typeof(Startup))]来定义这是OWIN的Startup类,当应用运行的时候会自动发现并调用Configuration方法
    • 在Startup类的Configuration方法中我们可以配置我们的Middleware。

    SystemWeb其实是实现了一个OwinHttpModule来根据MiddleWare的需求注册IIS的各种事件,然后执行相关的Middleware.

    先看一下IIS集成模式的HttpRequest处理的管道模型。

    我们的Middleware运行在IIS管道中哪个环节是通过调用appBuilder.UseStageMarker(PipelineStage.Authenticate);这个扩展方法来定义的。其中PipelineState有如下可选值:

    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,
    }

    以上枚举值对应IIS管道中的对应环节。Stage的指定有如下规则:

    1. 默认的Stage为PreHandlerExecute

    2. 每次UseStageMaker的调用指定了该次调用与前一次调用之间的注册的Middleware均在该次UseStageMaker中指定的Stage中运行

    3. Stage的指定顺序应该与IIS管道的处理顺序一致

    4. 如果Stage的指定顺序与IIS管道处理顺序不一致,在后面指定的在IIS管道中靠前的Stage会覆盖在其前面指定的在IIS管道中靠后的Stage.

    听上去有点绕口。其实理解了原因和实现就容易理解了。这种规则的原因是:IIS管道是有固定顺序的,而OWIN中Middleware的执行也是按照注册的先后顺序的,而当OWIN部署在IIS中时Middleware的执行又是依赖与IIS管道事件的,所以只有当指定的Stage顺序与IIS管道顺序一致时才不会有冲突。

    为Web Api添加Authenticate OWIN Middleware                                 

     我们首先创建一个普通的web api工程,添加如下接口,然后部署在IIS上。

    [RoutePrefix("api/persons")]
        public class PersonController : ApiController
        {
            [Route("{id}")]
            [Authorize]
            // GET api/values/5
            public string Get(int id)
            {
                return "Jensen";
            }
        }

    这时候如果去访问该接口会得到401未授权的错误。

    接下来通过添加AuthMiddleware来作为WebApi验证模块。

    首先添加AuthenticateMiddleware

    public class AuthenticateMiddleware
        {
            private Func<IDictionary<string, object>, Task> nextAppFunc;
            public AuthenticateMiddleware(Func<IDictionary<string, object>, Task> nextMiddleWareFunc)
            {
                nextAppFunc = nextMiddleWareFunc;
            }
    
            public async Task Invoke(IDictionary<string, object> parameters)
            {
                Trace.WriteLine("Auth Middleware");
                Trace.WriteLine(HttpContext.Current.CurrentNotification);
    
                var identity = new GenericIdentity("jensen");
                parameters["server.User"] = new GenericPrincipal(identity, new string[] { "admin" });
                if (nextAppFunc != null)
                {
                    await nextAppFunc.Invoke(parameters);
                }
            }
        }

    然后添加Startup类来注册AuthenticateMiddleware,并指定运行Stage为Authenticate.

    [assembly: OwinStartup(typeof(OwinIISHost.Startup))]
    namespace OwinIISHost
    {
        public class Startup
        {
            public void Configuration(IAppBuilder appBuilder)
            {
                appBuilder.Use<AuthenticateMiddleware>();
                appBuilder.UseStageMarker(PipelineStage.Authenticate);
                
            }
        }
    }

    这样当我们再次访问前面定义的api时,就能得到期望的结果了。因为在AuthenticateMiddleware中我们对所有请求都通过了验证。

    这里一开始有点疑问,WebApi中是根据HttpContext.User来获取当前请求的用户信息的。但是我们在AuthenticateMiddleware中并没有直接给HttpContext.User赋值,而是将User信息赋值到key 为server.user的OWIN环境参数中。这中间有个断档。通过查看SystemWeb Package的源码,解答了我的疑问。

    首先,我们的Middleware中接收到的OWIN环境参数类型为AspNetDictionary,可以查看其实现:

    internal AspNetDictionary(IPropertySource propertySource)//构造函数,这里propertySource由外部传入
            {
                _propertySource = propertySource;
            }
    
    
    object IDictionary<string, object>.this[string key]//索引属性,我们设置User信息给server.user key时该方法会被调用
            {
                get
                {
                    object value;
                    return PropertiesTryGetValue(key, out value) ? value : Extra[key];
                }
                set
                {
                    if (!PropertiesTrySetValue(key, value))
                    {
                        StrongExtra[key] = value;
                    }
                }
            }
    
    private bool PropertiesTrySetValue(string key, object value)
            {
                switch (key.Length)
                {
                    //....ignore some code here
                    case 11:
                        if (string.Equals(key, "server.User", StringComparison.Ordinal))
                        {
                            ServerUser = (IPrincipal)value;//赋值给ServerUser属性
                            return true;
                        }
                       break;
                    //...ignore some code here
                }
                return false;
            }
    
    internal IPrincipal ServerUser
            {
                get
                {
                    return _propertySource.GetServerUser();
                }
                set
                {
                    _propertySource.SetServerUser(value);//最后还是调了propertySource的SetServerUser方法
                }
            }

    那现在关键就看_propertySource是如何实现的了。通过查看创建AspNetDictionary的代码发现_propetySource为OwinCallContext类型的实例。看看它对SetServerUser的实现:

    void AspNetDictionary.IPropertySource.SetServerUser(IPrincipal value)
            {
                _httpContext.User = value;//真相大白,其实当我们给server.User key赋值时,value其实直接就赋给了HttpContext。
                Thread.CurrentPrincipal = value;
            }

    参考:

    AspNetKatana源码,包含SystemWebPackage

  • 相关阅读:
    编译安装centos6.9 php7.0 mysql5.6 nginx1.8
    mysql小细节随笔
    mpdf中文开发使用文档附demo实例
    svn和NetBeans一起使用造成svn老是死锁, database is locked
    Mastering MariaDB 神秘的MariaDB 中文翻译版
    Laravel开发采坑系列问题
    phpspider php爬虫框架
    ajaxFileUpload只能上传一次,和上传同名图片不能上传等bug问题
    bootstrap-treeview 中文开发手册
    phpredis Redis阵列 Redis Arrays
  • 原文地址:https://www.cnblogs.com/Code-life/p/7467318.html
Copyright © 2011-2022 走看看