zoukankan      html  css  js  c++  java
  • Asp.net Core启动流程讲解(三)

    Startup.cs启动前后,做了什么?以及如何从Startup到Webapi/Mvc流程接管?

    Startup

    UseStartup配置了Startup初始化

        public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
            public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>();
        }
    

    实际上Startup类是按照IStartup实现的非硬性约束的扩展

            public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
            {
    //省略不重要的代码段
    
                return hostBuilder
                    .ConfigureServices(services =>
                    {
                        if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                        {
                            services.AddSingleton(typeof(IStartup), startupType);
                        }
                        else
                        {
                            services.AddSingleton(typeof(IStartup), sp =>
                            {
                                var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
                                return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                            });
                        }
                    });
            }
    

    这里是不是豁然开朗?asp.net core其实内部依赖的是IStartup接口,至于Startup只是一个非IStartup硬性约束的实现

    	public interface IStartup
    	{
    		IServiceProvider ConfigureServices(IServiceCollection services);
    
    		void Configure(IApplicationBuilder app);
    	}
    

    Startup类依旧有一定既定约束

    1、需要实现ConfigureServices方法,参数为1,且类型为 IServiceCollection,返回值可为void/IServiceProvider(asp.net core 3.0以上,返回值只能为void)
    2、需要实现Configure,参数且为生命周期Singleton/Transient的Ioc容器内服务
    3、在ConfigureServices方法内配置DI,Configure内启用 中间件
    4、启动顺序由ConfigureServices->Configure

    中间件

    中间件由IApplicationBuilder扩展

    常见的IApplicationBuilder.UseMvc就是中间件,其实就是基于Route的一套扩展,本质上webapi/mvc,都是Route上层的一套扩展组件,这块可以翻阅源码,不具体展开了

    IApplicationBuilder.Use

    下面一段演示示例

                app.Use(async (context, next) =>
                {
                    Console.WriteLine("Use");
                    await next.Invoke();
                });
    
                app.Use(async (context, next) =>
                {
                    Console.WriteLine("Use1");
                    await next.Invoke();
                });
    
                app.UseMvc();
    

    先打印Use,然后Use1,最后完成执行。
    使用Use方法运行一个委托,我们可以在Next调用之前和之后分别执行自定义的代码,从而可以方便的进行日志记录等工作。这段代码中,使用next.Invoke()方法调用下一个中间件,从而将中间件管道连贯起来;如果不调用next.Invoke()方法,则会造成管道短路。

    IApplicationBuilder.Use是IApplicationBuilder Run/Map/MapWhe/Middleware的 核心 模块,基于IApplicationBuilder.Use做各种管道的扩展与实现。

    IApplicationBuilder.Run

                app.Run(async (context) =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
    
                app.UseMvc();
    

    很简单的示例,在默认api流程前,加了一段输出。段代码中,使用Run方法运行一个委托,这就是最简单的中间件,它拦截了所有请求,返回一段文本作为响应。Run委托终止了管道的运行,因此也叫作中断中间件。

    IApplicationBuilder Map/MapWhen

    Map创建基于路径匹配的分支、使用MapWhen创建基于条件的分支。
    创建一段IApplicationBuilder.Map的示例

                app.Map("/api/test", (_map) =>
                {
                    _map.Run(async (conetxt) =>
                    {
                        await conetxt.Response.WriteAsync("test");
                    });
                });
    

    访问 /api/test 路由地址时,浏览器输出 test 的字符串。

    再编写一段IApplicationBuilder.MapWhen 基于条件的分支示例

                app.Map("/api/test", (_map) =>
                {
                    _map.MapWhen((context) =>
                    {
                        return context.Request.Path == "/a";
                    },(__map) => {
                        __map.Run(async (conetxt) =>
                        {
                            await conetxt.Response.WriteAsync("test a");
                        });
                    });
    
                    _map.Run(async (conetxt) =>
                    {
                        await conetxt.Response.WriteAsync("test");
                    });
                });
    

    访问 /api/test 路由时,浏览器默认输出 test 字符串,当访问 /api/test/a 路由时,打印 test a 字符串。

    Middleware

    自定义一个Middleware

        public class ContentMiddleware
        {
            private RequestDelegate _nextDelegate;
    
            public ContentMiddleware(RequestDelegate nextDelegate)
            {
                _nextDelegate = nextDelegate;
            }
    
            public async Task Invoke(HttpContext httpContext)
            {
                if (httpContext.Request.Path.ToString().ToLower() == "/middleware")
                {
                    await httpContext.Response.WriteAsync(
                        "Handled by content middleware", Encoding.UTF8);
                }
                else
                {
                    await _nextDelegate.Invoke(httpContext);
                }
            }
        }
    

    访问路由 /middleware, 输出 "Handled by content middleware",反之则管道继续向下运行。

    IMiddleware

    UseMiddleware内部,判断类是否继承了IMiddleware,是则通过Ioc获取IMiddlewareFactory,通过IMiddlewareFactory 创建,然后在 IApplicationBuilder.Use 运行。
    反之则生成表达式在 IApplicationBuilder.Use 运行。

            public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
            {
                if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
                {
                    // IMiddleware doesn't support passing args directly since it's
                    // activated from the container
                    if (args.Length > 0)
                    {
                        throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
                    }
    
                    return UseMiddlewareInterface(app, middleware);
                }
    
                var applicationServices = app.ApplicationServices;
                return app.Use(next =>
                {
                    //省略代码段
    
                    var factory = Compile<object>(methodInfo, parameters);
    
                    return context =>
                    {
                        var serviceProvider = context.RequestServices ?? applicationServices;
                        if (serviceProvider == null)
                        {
                            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                        }
    
                        return factory(instance, context, serviceProvider);
                    };
                });
            }
    

    后记

    深挖了一下中间件的相关细节,也查阅了很多作者的分享文章,参考 Asp.Net Core 3.0 源码,重学了一下中间件这块
    如果对于内容有交流和学习的,可以加 .Net应用程序框架交流群,群号386092459

    分享一个公众号,关注学习/分享的

  • 相关阅读:
    (jmeter笔记)jmeter远程启用服务器(分布式)
    (jmeter笔记)jmeter打印日志
    (jmeter笔记)Jmeter正则表达式提取器获取Response hearders
    css3实现好看的边框效果
    简单递归写侧边菜单栏
    css3的transform-origin配合scale,控制动画,实现各种hover效果
    浅谈jQuery的promise
    tips07-encodeURI()的使用
    weui 的使用方法
    git 合并分支的时候会遇到的问题
  • 原文地址:https://www.cnblogs.com/NCoreCoder/p/13426223.html
Copyright © 2011-2022 走看看