墨刀做的网站设计,网页制作基本步骤,小波app推广网,昆明网络推广优化ASP.NET Core管道虽然在结构组成上显得非常简单#xff0c;但是在具体实现上却涉及到太多的对象#xff0c;所以我们在 “通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程”#xff08;上篇、中篇、下篇#xff09; 中围绕着一个经过极度简化的模拟管道讲述…ASP.NET Core管道虽然在结构组成上显得非常简单但是在具体实现上却涉及到太多的对象所以我们在 “通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程”上篇、中篇、下篇 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程。在本系列 中我们会还原构建模拟管道时可以舍弃和改写的部分向读者朋友们呈现一个真是的HTTP请求处理管道。 ASP.NET Core 的请求处理管道由一个服务器与一组有序排列的中间件构成前者仅仅完成请求监听、接收和响应这些与底层网络相关的工作至于请求接收之后和响应之前的所有工作都交给中间件来完成。ASP.NET Core的中间件通过一个类型FuncRequestDelegate, RequestDelegate的委托对象来表示而RequestDelegate也是一个委托它代表一项请求处理任务。
一、RequestDelegate
服务器接受到抵达的HTTP请求之后会构建一个描述当前请求的原始上下文服务器的类型决定了这个原始上下文的类型比如在我们模拟管道默认采用的HttpListenerServer由于采用HttpListener来监听、接收并响应请求所以它对应的原始上下文是一个HttpListenerContext对象。但是对于管道的后续部分即由注册的中间件构建的链表它们需要采用统一的方式来处理请求所以服务器最终会根据原始的上下文来创建一个抽象的HTTP上下文后者通过抽象类HttpContext来表示。
我们不仅可以利用这个HttpContext获取描述当前请求的上下文信息同样可以利用它来实现对响应的控制。针对当前请求的任何处理操作总是在这么一个上下文中进行所以一项请求处理任务完全可以抽象成一个类型FuncHttpContext,Task的委托来表示实际上具有如下定义的RequestDelegate委托具有类似的定义。 1: public delegate Task RequestDelegate(HttpContext context);
每个中间件都承载着独立的请求处理任务它本质上也体现了在当前HttpContext下针对请求的处理操作那么为什么中间件不直接通过一个RequestDelegate对象来表示而是表示为一个类型为FuncRequestDelegate, RequestDelegate的委托对象呢原因很简单中间件并不孤立地存在所有注册的中间件最终会根据注册的先后顺序组成一个链表每个中间件不仅仅需要完成各自的请求处理任务外还需要驱动链表中的下一个中间件。 如上图所示对于一个由多个FuncRequestDelegate, RequestDelegate对象组成的中间链表来说某个中间件会将后一个FuncRequestDelegate, RequestDelegate对象的返回值作为输入而自身的返回值则作为前一个中间件的输入。某个中间件执行之后返回的RequestDelegate对象不仅仅体现了自身对请求的处理操作而是体现了包含自己和后续中间件一次对请求的处理。那么对于第一个中间件来说它执行后返回的RequestDelegate对象实际上体现了整个应用对请求的处理逻辑。
二、 HttpContext
对当前上下文的抽象解除了管道对具体服务器类型的依赖 这使我们可以为ASP.NET Core应用自由地选择承载Hosting方式而不是像传统的ASP.NET应用一样只能寄宿在IIS之中。抽象HTTP上下文的目的是为了实现对请求处理流程的抽象只有这样我们才能将针对请求的某项操作体现在一个标准的中间件上有了这个这个标准化的中间件才有所谓的请求处理管道。
ASP.NET Core通过具有如下所示的HttpContext类来表示这么一个抽象的HTTP上下文。对于一个HttpContext对象来说它的核心体现在用于描述请求和响应的Request和Response属性之上。除此之外我们还可以通过它获取与当前请求相关的其他上下文信息比如用来控制用户认证的AuthenticationManager对象和代表当前请求用户的ClaimsPrincipal对象以及描述当前HTTP连接的ConnectionInfo对象和用于控制WebSocket的WebSocketManager。我们可以获取并控制当前会话也可以获取或者设置调试追踪的ID。 1: public abstract class HttpContext 2: { 3: 4: public abstract HttpRequest Request { get; } 5: public abstract HttpResponse Response { get; } 6: 7: public abstract AuthenticationManager Authentication { get; } 8: public abstract ClaimsPrincipal User { get; set; } 9: public abstract ConnectionInfo Connection { get; } 10: public abstract WebSocketManager WebSockets { get; } 11: public abstract ISession Session { get; set; } 12: public abstract string TraceIdentifier { get; set; } 13: public abstract CancellationToken RequestAborted { get; set; } 14: public abstract IDictionaryobject, object Items { get; set; } 15: 16: public abstract IServiceProvider RequestServices { get; set; } 17: public abstract IFeatureCollection Features { get; } 18: }
当需要中指对请求的处理时我们可以通过为RequestAborted属性设置一个CancellationToken对象从而将终止通知发送给管道。如果需要对整个管道共享一些与当前上下文相关的数据我们可以将它保存在通过Items属性表示的字典中。我们一再提到依赖注入被广泛地应用ASP.NET Core管道中HttpContext的RequestServices属性返回的根据在应用启动时注册的服务而创建的ServiceProvider。只要相应的服务被预先注册到指定的服务接口上我们就可能利用这个ServiceProvider根据这个接口得到对应的服务对象。 1: public abstract class HttpRequest 2: { 3: public abstract HttpContext HttpContext { get; } 4: public abstract string Method { get; set; } 5: public abstract string Scheme { get; set; } 6: public abstract bool IsHttps { get; set; } 7: public abstract HostString Host { get; set; } 8: public abstract PathString PathBase { get; set; } 9: public abstract PathString Path { get; set; } 10: public abstract QueryString QueryString { get; set; } 11: public abstract IQueryCollection Query { get; set; } 12: public abstract string Protocol { get; set; } 13: public abstract IHeaderDictionary Headers { get; } 14: public abstract IRequestCookieCollection Cookies { get; set; } 15: public abstract string ContentType { get; set; } 16: public abstract Stream Body { get; set; } 17: public abstract bool HasFormContentType { get; } 18: public abstract IFormCollection Form { get; set; } 19: 20: public abstract TaskIFormCollection ReadFormAsync(CancellationToken cancellationToken); 21: }
如上所示的是抽象类HttpRequest是对HTTP请求的描述它是HttpContext的只读属性Request的返回类型。我们可以利用这个对象获取到描述当前请求的各种相关信息比如请求的协议HTTP或者HTTPS、HTTP方法、地址也可以获取代表请求的HTTP消息的首部和主体。
在了解了表示请求的抽象类HttpRequest之后我们再来认识一个与之相对的用于描述响应HttpResponse类型。如下面的代码片断所示HttpResponse依然是一个抽象类我们可以通过定义在它之上的属性和方法来控制对请求的响应。从原则上讲我们对请求的所做的任意类型的响应都可以利用它来说实现。当我们通过表示当前上下文的HttpContext对象得到表示响应的HttpResponse之后我们不仅仅可以将希望的内容写入响应消息的主体还可以设置响应状态码以及添加相应的首部。 1: public abstract class HttpResponse 2: { 3: public abstract HttpContext HttpContext { get; } 4: public abstract int StatusCode { get; set; } 5: public abstract IHeaderDictionary Headers { get; } 6: public abstract Stream Body { get; set; } 7: public abstract long? ContentLength { get; set; } 8: public abstract IResponseCookies Cookies { get; } 9: public abstract bool HasStarted { get; } 10: 11: public abstract void OnStarting(Funcobject, Task callback, object state); 12: public virtual void OnStarting(FuncTask callback); 13: public abstract void OnCompleted(Funcobject, Task callback, object state); 14: public virtual void RegisterForDispose(IDisposable disposable); 15: public virtual void OnCompleted(FuncTask callback); 16: public virtual void Redirect(string location); 17: public abstract void Redirect(string location, bool permanent); 18: }
FeatureCollection
HttpContext的另一个只读属性Features返回一组“特性”对象。在ASP.NET Core管道式处理设计中特性是一个非常重要的概念特性是实现抽象化HttpContext的途径。具体来说服务器在接收到请求之后会创建一个由自身类型决定的原始的上下文管道不仅仅利用这个原始上下文来获取与请求相关的信息它对请求的最终响应实际上也是通过这个原始上下文来完成的。所以对一个HttpContext对象来说由它描述的上下文信息不仅仅来源于这个原始的上下文我们针对HttpContext所做的任何响应操作最终都需要分发给这个原始上下文来完成 否则是不会生效的。抽象的HttpContext和原始上下文之间的“双向绑定”究竟是如何实现的呢
这个所谓的“双向绑定”即使其实很简单。当原始上下文被创建出来之后服务器会将它封装成一系列标准的特性对象HttpContext正是对这些特性对象的封装。一般来说这些特性对象所对应的类型均实现了某个预定义的标准接口接口中不仅仅定义相应的属性来读写原始上下文中描述的信息还定义了相应的方法来操作原始上下文。HttpContext的属性Features返回的就是这组特性对象的集合它的返回类型为IFeatureCollection我们将实现了该接口的类型以及对应的对象统称为FeatureCollection。 1: public interface IFeatureCollection : IEnumerableKeyValuePairType, object 2: { 3: TFeature GetTFeature(); 4: void SetTFeature(TFeature instance); 5: 6: bool IsReadOnly { get; } 7: object this[Type key] { get; set; } 8: int Revision { get; } 9: }
一个FeatureCollection对象本质上就是一个Key和Value分别为Type和Object类型的字段话句话说特性对象通过对应的接口类型注册到HttpContext之上。我们通过调用Set方法将一个特性对象针对指定的类型一般为特性接口注册到这个字典对象上并通过Get方法根据注册的类型获取它。特性对象的注册和获取也可以利用定义的索引来完成。如果IsReadOnly属性返回True我们将不能注册新的特性或者修改已经注册的特性。 整数类型的之都属性Revision可以视为整个FeatureCollection对象的版本不论是采用何种方式注册新的特性还是修改现有的特性这个属性的值都将改变。
具有如下定义的FeatureCollection类实现了IFeatureCollection接口我们默认使用的FeatureCollection就是这么一个类型的对象。FeatureCollection具有两个构造函数重载默认无参构造函数帮助我们创建一个空的特性集合另一个构造函数则需要指定一个FeatureCollection对象来提供默认特性。对于采用第二个构造函数重载创建的 FeatureCollection对象来说当我们通过指定某个特性接口类型试图获取对应的特性对象时如果对应的特性没有注册到当前FeatureCollection对象上而是注册到提供默认特性的FeatureCollection对象上后者将会提供最终的特性。 1: public class FeatureCollection : IFeatureCollection 2: { 3: //其他成员 4: public FeatureCollection(); 5: public FeatureCollection(IFeatureCollection defaults); 6: }
对于FeatureCollection类型来说它 的IsReadOnly总是返回False所以它永远是可读可写的。对于调用默认无参构造函数创建的FeatureCollection对象来说它 的Revision默认返回零。如果我们通过指定另一个FeatureCollection对象为参数调用第二个构造函数来创建一个FeatureCollection对象前者的Revision属性值将成为后者同名属性的默认值。不论我们采用何种形式调用Set方法或者索引添加一个新的特性或者改变了一个已经注册的特性FeatureCollection对象的Revision属性都将自动递增。上述的这些关于FeatureCollection的特性都体现在如下所示的代码片段中。 1: FeatureCollection defaults new FeatureCollection(); 2: Debug.Assert(defaults.Revision 0); 3: 4: defaults.SetIFoo(new Foo()); 5: Debug.Assert(defaults.Revision 1); 6: 7: defaults[typeof(IBar)] new Bar(); 8: Debug.Assert(defaults.Revision 2); 9: 10: FeatureCollection features new FeatureCollection(defaults); 11: Debug.Assert(features.Revision 2); 12: Debug.Assert(features.GetIFoo().GetType() typeof(Foo)); 13: 14: features.SetIBaz(new Baz()); 15: Debug.Assert(features.Revision 3);
DefaultHttpContext
ASP.NET Core默认使用的HttpContext类型为DefaultHttpContext上面我们介绍的针对描述原始上下文“特性集合”来创建HttpContext的策略就体现在这个类型之上。DefaultHttpContext具有一个如下的构造函数作为参数的FeatureCollection对象就是这么一个特性集合。 1: public class DefaultHttpContext : HttpContext 2: { 3: public DefaultHttpContext(IFeatureCollection features); 4: }
不论是组成管道的中间件还是建立在管道上的应用在默认的情况下都是利用这个DefaultHttpContext对象来获取当前请求的相关信息并利用这个对象来控制最终发送的响应。但是DefaultHttpContext对象这个这个过程中仅仅是一个“代理”针对它的调用属性或者方法最终都需要转发给由具体服务器创建的那个原始上下文在构造函数中指定的这个FeatureCollection对象所代表的特性集合成为了这两个上下文对象进行沟通的唯一渠道。对于定义在DefaultHttpContext中的所有属性它们几乎都具有一个对应的特性这些特性都对应着一个接口。表1列出了部分特性接口以及DefaultHttpContext对应的属性。
表1 描述原始HTTP上下文的特性接口 接口属性描述IHttpRequestFeatureRequest获取描述请求的基本信息。IHttpResponseFeatureResponse控制对请求的响应。IHttpAuthenticationFeatureAuthenticationManger/User提供完成用户认证的AuthenticationHandler对象和表示当前用户的ClaimsPrincipal对象IHttpConnectionFeatureConnection提供描述当前HTTP连接的基本信息。IItemsFeatureItems提供用户存放针对当前请求的对象容器。IHttpRequestLifetimeFeatureRequestAborted传递请求处理取消通知和中止当前请求处理。IServiceProvidersFeatureRequestServices提供根据服务注册创建的ServiceProvider。ISessionFeatureSession提供描述当前会话的Session对象。IHttpRequestIdentifierFeatureTraceIdentifier为追踪日志Trace提供针对当前请求的唯一标识。IHttpWebSocketFeatureWebSockets管理WebSocket 对于上面列出的众多特性接口我们在后续相关章节中都会涉及到目前来说我们只需要了解一下两个最重要的特性接口即表示请求和响应的IHttpRequestFeature和IHttpResponseFeature。从下面给出的代码片断我们不难看出这两个接口的定义分别与抽象类HttpRequest和HttpResponse具有一致的定义。对于DefaultHttpContext类型来说它的Request和Response属性分别返回的是一个DefaultHttpRequest和DefaultHttpResponse对象。DefaultHttpRequest和DefaultHttpResponse分别继承自HttpRequest和HttpResponse它们分别利用这个两个特性实现了从基类继承下来的所有抽象成员。 1: public interface IHttpRequestFeature 2: { 3: Stream Body { get; set; } 4: IHeaderDictionary Headers { get; set; } 5: string Method { get; set; } 6: string Path { get; set; } 7: string PathBase { get; set; } 8: string Protocol { get; set; } 9: string QueryString { get; set; } 10: string Scheme { get; set; } 11: } 12: 13: public interface IHttpResponseFeature 14: { 15: Stream Body { get; set; } 16: bool HasStarted { get; } 17: IHeaderDictionary Headers { get; set; } 18: string ReasonPhrase { get; set; } 19: int StatusCode { get; set; } 20: 21: void OnCompleted(Funcobject, Task callback, object state); 22: void OnStarting(Funcobject, Task callback, object state); 23: }
对于实现请求监听、接收和响应的服务器来说它们都需要通过实现上面这些特性接口来定义针对性的特性类。如下图所示当成功接收到请求之后服务器会创建相应的特性并将它们组合成一个FeatureCollection对象最后创建出一个DefaultHttpContext对象我们注册的所有中间件针对这个DefaultHttpContext完成各自的请求处理工作。 HttpContextFactory
在服务器接收到抵达的请求时它并不会直接利用原始的上下文去创建HttpContext对象HttpContext在管道中的创建是间接地通过HttpContextFactory来完成的。 HttpContextFactory是对所有实现了IHttpContextFactory接口的所有类型及其对象的统称。如下面的代码片段所示IHttpContextFactory接口除了定义创建HttpContext对象的Create方法之外还定义了另一个方法Dispose来释放指定的HttpContext对象。HttpContextFactory类是该接口的默认实现者由它的Create方法创建并返回的自然是一个DefaultHttpContext对象。 1: public interface IHttpContextFactory 2: { 3: HttpContext Create(IFeatureCollection featureCollection); 4: void Dispose(HttpContext httpContext); 5: } 6: 7: public class HttpContextFactory : IHttpContextFactory 8: { 9: //省略其他成员 10: public HttpContext Create(IFeatureCollection featureCollection); 11: public void Dispose(HttpContext httpContext); 12: }
综上所述组成管道的所有中间件在一个标准化的上下文中完整对请求的处理这个上下文通过抽象类HttpContext表示ASP.NET Core默认使用的是它的子类DefaultHttpContext。一个DefaultHttpContext对象是根据描述原始上下文的特性集合每个特性对应的类型都实现了标准的接口接口IHttpRequestFeature和IHttpResponseFeature分别代表针对请求和响应的特性。HttpContext默认情况下是通过注册的工厂创建的该工厂通过接口IHttpContextFactory表示默认使用的HttpContext工厂类型为HttpContextFactory它也是DefaultHttpContext对象的创建者。
三、ApplicationBuilder
以类型为FuncRequestDelegate, RequestDelegate的委托对象表示的中间件需要在启动的时候注册到应用程序上。所有注册的中间件最终会转换成一个RequestDelegate类型的委托对象它们按照注册顺序对请求的处理流程最终体现在对这个委托对象的执行。不论是最终将中间件转换成RequestDelegate对象还是最初对它们的注册都是通过一个名为ApplicationBuilder的对象来完成的。
ApplicationBuilder是我们对所有实现了IApplicationBuilder接口的所有类型以及对应对象的统称。接口IApplicationBuilder定义如下中间件的注册和RequestDelegate对象的生成分别通过调用它的Use和Build方法来完成。除了这两个核心方法IApplicationBuilder接口还定义了三个属性其中ApplicationServices返回根据最初服务注册生成的ServiceProvider对象而ServerFeatures属性返回的FeatureCollection对象是描述Server的特性集合。字典类型的Properties属性用户存储任意自定义的属性而New方法会根据自己“克隆”出一个新的ApplicationBuilder对象这两个ApplicationBuilder对象应用具有相同的属性集合。 1: public interface IApplicationBuilder 2: { 3: IServiceProvider ApplicationServices { get; set; } 4: IFeatureCollection ServerFeatures { get; } 5: IDictionarystring, object Properties { get; } 6: 7: RequestDelegate Build(); 8: IApplicationBuilder New(); 9: IApplicationBuilder Use(FuncRequestDelegate, RequestDelegate middleware); 10: }
具有如下定义的ApplicationBuilder类型是对IApplicationBuilder接口的默认实现。ApplicationBuilder类型利用一个ListFuncRequestDelegate, RequestDelegate对象来保存注册的中间件所以Use方法只需要将指定的中间件添加到这个列表中即可而Build方法只需要逆序调用这些注册的中间件对应的FuncRequestDelegate, RequestDelegate对象就能得到我们需要的RequestDelegate对象。值得一提的是Build方法实际上在中间件链条的尾部添加了一个额外的中间件该中间件会负责将响应状态码设置为404如果我们没有注册一个中间件对请求作最终的响应这样的中间件将不会试图调用后续中间件整个管道比较回复一个状态码为404的响应。 1: public class ApplicationBuilder : IApplicationBuilder 2: { 3: private readonly IListFuncRequestDelegate, RequestDelegate middlewares new ListFuncRequestDelegate, RequestDelegate(); 4: 5: public IDictionarystring, object Properties { get; } 6: 7: public IServiceProvider ApplicationServices 8: { 9: get { return GetPropertyIServiceProvider(application.Services); } 10: set { SetPropertyIServiceProvider(application.Services, value); } 11: } 12: 13: public IFeatureCollection ServerFeatures 14: { 15: get { return GetPropertyIFeatureCollection(server.Features); } 16: } 17: 18: 19: public ApplicationBuilder(IServiceProvider serviceProvider) 20: { 21: this.Properties new Dictionarystring, object(); 22: ApplicationServices serviceProvider; 23: } 24: 25: public ApplicationBuilder(IServiceProvider serviceProvider, object server) 26: : this(serviceProvider) 27: { 28: SetProperty(server.Features, server); 29: } 30: 31: public IApplicationBuilder Use(FuncRequestDelegate, RequestDelegate middleware) 32: { 33: middlewares.Add(middleware); 34: return this; 35: } 36: 37: public IApplicationBuilder New() 38: { 39: return new ApplicationBuilder(this); 40: } 41: 42: public RequestDelegate Build() 43: { 44: RequestDelegate app context 45: { 46: context.Response.StatusCode 404; 47: return Task.FromResult(0); 48: }; 49: foreach (var component in middlewares.Reverse()) 50: { 51: app component(app); 52: } 53: return app; 54: } 55: 56: private ApplicationBuilder(ApplicationBuilder builder) 57: { 58: this.Properties builder.Properties; 59: } 60: 61: private T GetPropertyT(string key) 62: { 63: object value; 64: return Properties.TryGetValue(key, out value) ? (T)value : default(T); 65: } 66: 67: private void SetPropertyT(string key, T value) 68: { 69: this.Properties[key] value; 70: } 71: }
通过上面的代码片段我们不难看到不论是通过ApplicationServices属性返回的ServiceProvider对象还是通过ServerFeatures属性返回的用于描述Server特性的FeatureCollection对象它们实际上都保存在通过Properties属性返回字典对象上。ApplicationBuilder具有两个公共构造函数重载它们具有一个公共的参数即用来初始化ApplicationServices属性的参数serviceProvider。
一个构造函数具有一个名为server的参数但是这个参数并不是表示管道使用的服务器而是承载服务器相关特性的FeatureCollection对象不过这个参数类型被定义成Object而不是IFeatureCollection接口。New方法直接调用私有构造函数创建出一个新的ApplicationBuilder对象这个对象与自己的Properties属性共享同一个字典对象由于ApplicationServices和ServerFeatures属性的返回值也存放在这个字典对象上所以New方法得到的ApplicationBuilder对象与自身对象其实是完全等效的。
ApplicationBuilderFactory
ApplicationBuilderFactory是ASP.NET Core它用来创建ApplicationBuilder的工厂它是对所有实现了接口IApplicationBuilderFactory的所有类型以及对应对象的统称。如下面的代码片段所示该接口定义了唯一个方法CreateBuilder根据提供的FeatureCollection对象创建出对应的ApplicationBuilder对象这个FeatureCollection对象正是承载与服务器相关特性的集合。ApplicationBuilderFactory类型是该接口的默认实现者当CreateBuilder方法被调用的时候它会直接将构造时提供ServiceProvider对象和serverFeatures参数表示的FeatureCollection对象来创建返回的ApplicationBuilder对象。 1: public interface IApplicationBuilderFactory 2: { 3: IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures); 4: } 5: 6: public class ApplicationBuilderFactory : IApplicationBuilderFactory 7: { 8: private readonly IServiceProvider _serviceProvider; 9: 10: public ApplicationBuilderFactory(IServiceProvider serviceProvider) 11: { 12: this._serviceProvider serviceProvider; 13: } 14: 15: public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures) 16: { 17: return new ApplicationBuilder(_serviceProvider, serverFeatures); 18: } 19: }
中间件类型
虽然中间件最终体现为一个类型为 FuncRequestDelegate, RequestDelegate的委托对象但是我们在大部分情况下都会将中间件定义成一个单独的类型。虽然这样的中间件类型不要求实现某个预定义的接口或者继承某个预定义的基类但是却要遵守几个必要的约定。接下来我们直接如下这个ContentMiddleware类说说一个合法的中间件类型应该如何定义。 1: public class ContentMiddleare 2: { 3: public RequestDelegate _next; 4: public byte[] _content; 5: public string _contentType; 6: 7: public ContentMiddleare(RequestDelegate next, byte[] content, string contentType) 8: { 9: _next next; 10: _content content; 11: _contentType contentType; 12: } 13: 14: public async Task Invoke(HttpContext context, ILoggerFactory loggerFactory) 15: { 16: loggerFactory.CreateLoggerContentMiddleare().LogInformation($Write content ({_contentType})); 17: context.Response.ContentType _contentType; 18: await context.Response.Body.WriteAsync(_content,0, _content.Length); 19: } 20: }
如上所示的这个中间件(ContentMiddleware)可以帮助我们将任何类型的内容响应给客户端它的两个字段_content和_contentType分别代表响应内容和媒体类型内容类型或者MIME类型它体现了一个典型中间件类型的定义规则或者约定
应该定义成实例类不能定义成静态类。具有一个有效的公共构造函数。这个构造函数的第一个参数类型必须为RequestDelegate代表对请求的后续操作可以视为下一个注册的中间件至于后续参数的个数和类型则不作要求。针对请求的处理定义在一个名为Invoke的公共实例方法其返回类型为Task。该方法的第一个参数类型为HttpContext代表当前HTTP上下文。我们可以为这个方法定义任意数量和类型的后续参数当这个方法被执行的时候系统将会采用依赖注入的方式提供响应的服务来为这个参数赋值。
中间件类型的注册
中间件类型的注册可以通过调用 IApplicationBuilder接口的扩展方法UseMiddleware 和UseMiddleware TMiddleware 进行注册。如下面的代码片断所示除了指定中间件的类型之外我们还需要按照顺序指定调用目标构造函数的全部或者部分参数。不过不过的参数列表不需要提供作为第一个参数的RequestDelegate如果仅仅指定了部分参数缺失的参数将会自动通过ServiceProvider来提供。 1: public static class UseMiddlewareExtensions 2: { 3: public static IApplicationBuilder UseMiddlewareTMiddleware(this IApplicationBuilder app, params object[] args); 4: public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args); 5: }
对于上面定义的这个 ContentMiddleare类型我们按照如下的方式对它进行了注册。当这个中间件执行的时候它会响应客户端一张PNG图片。如果客户端是能够支持图片呈现的浏览器这张图片会直接显示在浏览器上。 1: new WebHostBuilder() 2: .Configure(appapp.UseMiddlewareContentMiddleare(File.ReadAllBytes(girl.png),image/png)) 3: ...
虽然中间件可以定义成任何一个遵循约定的类型但是中间件自身在ASP.NET Core框架中总是体现为一个类型为FuncRequestDelegate, RequestDelegate的委托对象所以上述的这个UseMiddleware方法在执行的时候需要在内部根据注册的中间件类型和指定的参数列表创建这么一个FuncRequestDelegate, RequestDelegate对象。其中的逻辑并不复杂它之需要将中间件对象的创建和针对Invoke方法的调用实现在返回的委托对象中就可以了。值得一提的是针对Invoke方法的调用并没有直接通过反射的方式来实现而是采用表达式后者具有更好的性能。在如下所示的代码片段中我采用最精简的代码模拟了UseMiddleware方法的实现。 1: public static class WebHostBuilderExtensions 2: { 3: private static MethodInfo GetServiceMethod typeof(WebHostBuilderExtensions).GetMethod(GetService, BindingFlags.Static | BindingFlags.NonPublic); 4: 5: public static IApplicationBuilder UseMiddlewareTMiddleware(this IApplicationBuilder app, params object[] args) 6: { 7: return UseMiddleware2(app, typeof(TMiddleware), args); 8: } 9: 10: public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middlewareType, params object[] args) 11: { 12: return app.Use(next 13: { 14: return context { 15: var factory Compileobject(middlewareType.GetMethod(Invoke, BindingFlags.Instance | BindingFlags.Public)); 16: object middleware CreateMiddleware(app, middlewareType, next, args); 17: return factory(middleware, context, app.ApplicationServices); 18: }; 19: }); 20: } 21: 22: private static object CreateMiddleware(IApplicationBuilder app, Type middlewareType, RequestDelegate next, params object[] args) 23: { 24: object[] arguments new object[args.Length 1]; 25: arguments[0] next; 26: args.CopyTo(arguments, 1); 27: return ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewareType, arguments); 28: } 29: 30: //将对Invoke方法的调用转换成一个FuncTMiddleware, HttpContext, IServiceProvider, Task对象 31: private static FuncTMiddleware, HttpContext, IServiceProvider, Task CompileTMiddleware(MethodInfo invokeMethod) 32: { 33: ParameterExpression middleware Expression.Parameter(typeof(TMiddleware), middleware); 34: ParameterExpression httpContext Expression.Parameter(typeof(HttpContext), httpContext); 35: ParameterExpression serviceProvider Expression.Parameter(typeof(IServiceProvider), serviceProvider); 36: 37: var arguments from parameter in invokeMethod.GetParameters() 38: select GetArgument(httpContext, serviceProvider, parameter.ParameterType); 39: 40: Expression instance middleware; 41: if (invokeMethod.DeclaringType ! typeof(TMiddleware)) 42: { 43: instance Expression.Convert(instance, invokeMethod.DeclaringType); 44: } 45: 46: Expression invoke Expression.Call(instance, invokeMethod, arguments.ToArray()); 47: return Expression.LambdaFuncTMiddleware, HttpContext, IServiceProvider, Task(invoke, middleware, httpContext, serviceProvider).Compile(); 48: } 49: 50: //生成调用Invoke方法的参数表达式 51: private static Expression GetArgument(Expression httpContext, Expression serviceProvider, Type parameterType) 52: { 53: if (parameterType typeof(HttpContext)) 54: { 55: return httpContext; 56: } 57: Expression serviceType Expression.Constant(parameterType, typeof(Type)); 58: Expression callGetService Expression.Call(GetServiceMethod, serviceProvider, serviceType); 59: return Expression.Convert(callGetService, parameterType); 60: } 61: 62: private static object GetService(IServiceProvider serviceProvider, Type serviceType) 63: { 64: return serviceProvider.GetService(serviceType); 65: } 66: }
原文地址http://www.cnblogs.com/artech/p/asp-net-core-real-pipeline-01.html .NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注