目录
  • 准备工作:一份asp.net core web api应用程序
  • startup类
    • startup构造函数
    • configureservices
    • configure
  • 省略startup类
    • istartupfilter
      • ihostingstartup
        • hostingstartup 程序集
        • hostingstartup 特性
        • 激活hostingstarup程序集
          • 1.使用环境变量(推荐)
          • 2.在程序中配置
      • 多环境配置
        • 环境配置方式
          • 基于环境的 startup
            • 1.将iwebhostenvironment注入 startup 类
            • 2.startup 方法约定
            • 3.startup 类约定

        准备工作:一份asp.net core web api应用程序

        当我们来到一个陌生的环境,第一件事就是找到厕所在哪。

        当我们接触一份新框架时,第一件事就是找到程序入口,即main方法

        public class program
        {
            public static void main(string[] args)
            {
                createhostbuilder(args).build().run();
            }
        
            public static ihostbuilder createhostbuilder(string[] args) =>
                host.createdefaultbuilder(args)
                    .configurewebhostdefaults(webbuilder =>
                    {
                        webbuilder.usestartup<startup>();
                    });
        }

        代码很简单,典型的建造者模式:通过ihostbuilder创建一个通用主机(generic host),然后启动它(至于什么是通用主机,咱们后续的文章会说到)。咱们不要一上来就去研究createdefaultbuilderconfigurewebhostdefaults这些方法的源代码,应该去寻找能看的见、摸得着的,很明显,只有startup

        startup类

        startup类承担应用的启动任务,所以按照约定,起名为startup,不过你可以修改为任意类名(强烈建议类名为startup)。

        默认的startup结构很简单,包含:

        • 构造函数
        • configuration属性
        • configureservices方法
        • configure方法
        public class startup
        {
            public startup(iconfiguration configuration)
            {
                configuration = configuration;
            }
        
            public iconfiguration configuration { get; }
        
            // this method gets called by the runtime. use this method to add services to the container.
            // 该方法由运行时调用,使用该方法向di容器添加服务
            public void configureservices(iservicecollection services)
            {
            }
        
            // this method gets called by the runtime. use this method to configure the http request pipeline.
            // 该方法由运行时调用,使用该方法配置http请求管道
            public void configure(iapplicationbuilder app, iwebhostenvironment env)
            {
            }
        }

        startup构造函数

        当使用通用主机(generic host)时,startup构造函数支持注入以下三种服务类型:

        • iconfiguration
        • iwebhostenvironment
        • ihostenvironment
        public startup(
            iconfiguration configuration,
            ihostenvironment hostenvironment,
            iwebhostenvironment webhostenvironment)
        {
            configuration = configuration;
            hostenvironment = hostenvironment;
            webhostenvironment = webhostenvironment;
        }
        
        public iconfiguration configuration { get; }
        
        public ihostenvironment hostenvironment { get; set; }
        
        public iwebhostenvironment webhostenvironment { get; set; }

        这里你会发现 hostenvironmentwebhostenvironment 的实例是同一个。别着急,后续文章我们聊到host的时候,你就明白了。

        configureservices

        • 该方法是可选的
        • 该方法用于添加服务到di容器中
        • 该方法在configure方法之前被调用
        • 该方法要么无参数,要么只能有一个参数且类型必须为iservicecollection
        • 该方法内的代码大多是形如add{service}的扩展方法

        常用的服务有(部分服务框架已默认注册):

        • addcontrollers:注册controller相关服务,内部调用了addmvccoreaddapiexploreraddauthorizationaddcorsadddataannotationsaddformattermappings等多个扩展方法
        • addoptions:注册options相关服务,如ioptions<>ioptionssnapshot<>ioptionsmonitor<>ioptionsfactory<>ioptionsmonitorcache<>等。很多服务都需要options,所以很多服务注册的扩展方法会在内部调用addoptions
        • addrouting:注册路由相关服务,如iinlineconstraintresolverlinkgeneratoriconfigureoptions<routeoptions>routepatterntransformer
        • addaddlogging:注册logging相关服务,如iloggerfactoryilogger<>iconfigureoptions<loggerfilteroptions>>
        • addauthentication:注册身份认证相关服务,以方便后续注册jwtbearer、cookie等服务
        • addauthorization:注册用户授权相关服务
        • addmvc:注册mvc相关服务,比如controllers、views、razorpages等
        • addhealthchecks:注册健康检查相关服务,如healthcheckserviceihostedservice

        configure

        • 该方法是必须的
        • 该方法用于配置http请求管道,通过向管道添加中间件,应用不同的响应方式。
        • 该方法在configureservices方法之后被调用
        • 该方法中的参数可以接受任何已注入到di容器中的服务
        • 该方法内的代码大多是形如use{middleware}的扩展方法
        • 该方法内中间件的注册顺序与代码的书写顺序是一致的,先注册的先执行,后注册的后执行

        常用的中间件有

        • usedeveloperexceptionpage:当发生异常时,展示开发人员异常信息页。如图

        • userouting:路由中间件,根据url中的路径导航到对应的endpoint。必须与useendpoints搭配使用。
        • useendpoints:执行路由所选择的endpoint对应的委托。
        • useauthentication:身份认证中间件,用于对请求用户的身份进行认证。比如,早晨上班打卡时,管理员认出你是公司员工,那么才允许你进入公司。
        • useauthorization:用户授权中间件,用于对请求用户进行授权。比如,虽然你是公司员工,但是你是一名.net开发工程师,那么你只允许坐在.net开发工程师区域的工位上,而不能坐在老总的办公室里。
        • usemvc:mvc中间件。
        • usehealthchecks:健康检查中间件。
        • usemiddleware:用来添加匿名中间件的,通过该方法,可以方便的添加自定义中间件。

        省略startup类

        另外,startup类也可以省略,直接进行如下配置即可(虽然可以这样做,但是不推荐):

        public static ihostbuilder createhostbuilder(string[] args) =>
            host.createdefaultbuilder(args)
                .configurewebhostdefaults(webbuilder =>
                {
                    // configureservices 可以调用多次,最终会将结果聚合
                    webbuilder.configureservices(services =>
                    {
                    })
                    // configure 如果调用多次,则只有最后一次生效
                    .configure(app =>
                    {
                        var env = app.applicationservices.getrequiredservice<iwebhostenvironment>();
                    });
                });

        istartupfilter

        public interface istartupfilter
        {
            action<iapplicationbuilder> configure(action<iapplicationbuilder> next);
        }

        有时,我们想要将一系列相关中间件的注册封装到一起,那么我们只需要通过实现istartupfilter,并在startup.configureservices中配置istartupfilter的依赖注入即可。

        • istartupfilter中配置的中间件,总是比startup类中configure方法中的中间件先注册;对于多个istartupfilter实现,执行顺序与服务注册时的顺序一致

        我们可以通过一个例子来验证一下中间件的注册顺序。

        首先是三个istartupfilter的实现类:

        public class firststartupfilter : istartupfilter
        {
            public action<iapplicationbuilder> configure(action<iapplicationbuilder> next)
            => app =>
            {
                app.use((context, next) =>
                {
                    console.writeline("first");
                    return next();
                });
                next(app);
            };
        }
        
        public class secondstartupfilter : istartupfilter
        {
            public action<iapplicationbuilder> configure(action<iapplicationbuilder> next)
            => app =>
            {
                app.use((context, next) =>
                {
                    console.writeline("second");
                    return next();
                });
                next(app);
            };
        }
        
        public class thirdstartupfilter : istartupfilter
        {
            public action<iapplicationbuilder> configure(action<iapplicationbuilder> next)
            => app =>
            {
                app.use((context, next) =>
                {
                    console.writeline("third");
                    return next();
                });
                next(app);
            };
        }

        接下来进行注册:

        public static ihostbuilder createhostbuilder(string[] args) =>
            host.createdefaultbuilder(args)
                .configureservices(services =>
                {
                    // 第一个被注册
                    services.addtransient<istartupfilter, firststartupfilter>();
                })
                .configurewebhostdefaults(webbuilder =>
                {
                    webbuilder.usestartup<startup>();
                })
                .configureservices(services => 
                {
                    // 第三个被注册
                    services.addtransient<istartupfilter, thirdstartupfilter>();
                });
                
        public class startup
        {
            public void configureservices(iservicecollection services)
            {
                // 第二个被注册
                services.addtransient<istartupfilter, secondstartupfilter>();
            }
        
            public void configure(iapplicationbuilder app, iwebhostenvironment env)
            {
                // 第四个被注册
                app.use((context, next) =>
                {
                    console.writeline("forth");
                    return next();
                });
            }
        }

        最后通过输出可以看到,执行顺序的确是这样子的。

        first
        second
        third
        forth

        ihostingstartup

        istartupfilter不同的是,ihostingstartup可以在启动时通过外部程序集向应用增加更多功能。不过这要求必须调用configurewebhostconfigurewebhostdefaults等类似用来配置web主机的扩展方法

        我们经常使用的nuget包skyapm.agent.aspnetcore就使用了该特性。

        下面我们就来看一下该如何使用它。

        hostingstartup 程序集

        要创建hostingstartup程序集,可以通过创建类库项目或无入口点的控制台应用来实现。

        接下来咱们还是看一下上面提到过的skyapm.agent.aspnetcore

        using skyapm.agent.aspnetcore;
        
        [assembly: hostingstartup(typeof(skyapmhostingstartup))]
        
        namespace skyapm.agent.aspnetcore
        {
            internal class skyapmhostingstartup : ihostingstartup
            {
                public void configure(iwebhostbuilder builder)
                {
                    builder.configureservices(services => services.addskyapm(ext => ext.addaspnetcorehosting()));
                }
            }
        }

        该hostingstartup类:

        • 实现了ihostingstartup接口
        • configure方法中使用iwebhostbuilder来添加增强功能
        • 配置了hostingstartup特性

        hostingstartup 特性

        hostingstartup特性用于标识哪个类是hostingstartup类,hostingstartup类需要实现ihostingstartup接口。

        当程序启动时,会自动扫描入口程序集和配置的待激活的的程序集列表(参见下方:激活hostingstarup程序集),来找到所有的hostingstartup特性,并通过反射的方式创建startup并调用configure方法。

        skyapm.agent.aspnetcore为例

        using skyapm.agent.aspnetcore;
        
        [assembly: hostingstartup(typeof(skyapmhostingstartup))]
        
        namespace skyapm.agent.aspnetcore
        {
            internal class skyapmhostingstartup : ihostingstartup
            {
                public void configure(iwebhostbuilder builder)
                {
                    builder.configureservices(services => services.addskyapm(ext => ext.addaspnetcorehosting()));
                }
            }
        }

        激活hostingstarup程序集

        要激活hostingstarup程序集,我们有两种配置方式:

        1.使用环境变量(推荐)

        使用环境变量,无需侵入程序代码,所以我更推荐大家使用这种方式。

        配置环境变量aspnetcore_hostingstartupassemblies,多个程序集使用分号(;)进行分隔,用于添加要激活的程序集。变量webhostdefaults.hostingstartupassemblieskey就是指代这个环境变量的key。

        另外,还有一个环境变量,叫做aspnetcore_hostingstartupexcludeassemblies,多个程序集使用分号(;)进行分隔,用于排除要激活的程序集。变量webhostdefaults.hostingstartupexcludeassemblieskey就是指代这个环境变量的key。

        我们在 launchsettings.json 中添加两个程序集:

        "environmentvariables": {
            "aspnetcore_hostingstartupassemblies": "skyapm.agent.aspnetcore;hostingstartuplibrary"
        }

        2.在程序中配置

        public static ihostbuilder createhostbuilder(string[] args) =>
            host.createdefaultbuilder(args)
                .configurewebhostdefaults(webbuilder =>
                {
                    webbuilder.usesetting(
                        webhostdefaults.hostingstartupassemblieskey,
                        "skyapm.agent.aspnetcore;hostingstartuplibrary")
                    .usestartup<startup>();
                });

        这样就配置完成了,很的一个功能点吧!

        需要注意的是,无论使用哪种配置方式,当存在多个hostingstartup程序集时,将按配置这些程序集时的书写顺序执行 configure方法。

        多环境配置

        一款软件,一般要经过需求分析、设计编码,单元测试、集成测试以及系统测试等一系列测试流程,验收,最终上线。那么,就至少需要4套环境来保证系统运行:

        • development:开发环境,用于开发人员在本地对应用进行调试运行
        • test:测试环境,用于测试人员对应用进行测试
        • staging:预发布环境,用于在正式上线之前,对应用进行集成、测试和预览,或用于验收
        • production:生产环境,应用的正式线上环境

        环境配置方式

        通过环境变量aspnetcore_environment指定运行环境

        注意:如果未指定环境,默认情况下,为 production

        在项目的properties文件夹里面,有一个“launchsettings.json”文件,该文件是用于配置vs中项目启动的。
        接下来我们就在launchsettings.json中配置一下。
        先解释一下该文件中出现的几个参数:

        • commandname:指定要启动的web服务器,有三个可选值:

        project:启动 kestrel

        iisexpress:启动iis express

        iis:不启用任何web服务器,使用iis

        • dotnetrunmessages:bool字符串,指示当使用 dotnet run 命令时,终端能够及时响应并输出消息,具体参考stackoverflow和github issue
        • launchbrowser:bool值,指示当程序启动后,是否打开浏览器
        • launchurl:默认启动路径
        • applicationurl:应用程序url列表,多个url之间使用分号(;)进行分隔。当launchbrowser为true时,将{applicationurl}/{launchurl}作为浏览器默认访问的url
        • environmentvariables:环境变量集合,在该集合内配置环境变量
        {
          "$schema": "http://json.schemastore.org/launchsettings.json",
          "profiles": {
            // 如果不指定profile,则默认选择第一个
            // development
            "asp.net.webapi": {
              "commandname": "project",
              "dotnetrunmessages": "true",
              "launchbrowser": true,
              "launchurl": "weatherforecast",
              "applicationurl": "http://localhost:5000",
              "environmentvariables": {
                "aspnetcore_environment": "development"
              }
            },
            // test
            "asp.net.webapi.test": {
              "commandname": "project",
              "dotnetrunmessages": "true",
              "launchbrowser": true,
              "launchurl": "weatherforecast",
              "applicationurl": "http://localhost:5000",
              "environmentvariables": {
                "aspnetcore_environment": "test"
              }
            },
            // staging
            "asp.net.webapi.staging": {
              "commandname": "project",
              "dotnetrunmessages": "true",
              "launchbrowser": true,
              "launchurl": "weatherforecast",
              "applicationurl": "http://localhost:5000",
              "environmentvariables": {
                "aspnetcore_environment": "staging"
              }
            },
            // production
            "asp.net.webapi.production": {
              "commandname": "project",
              "dotnetrunmessages": "true",
              "launchbrowser": true,
              "launchurl": "weatherforecast",
              "applicationurl": "http://localhost:5000",
              "environmentvariables": {
                "aspnetcore_environment": "production"
              }
            },
            // 用于测试在未指定环境时,默认是否为production
            "asp.net.webapi.default": {
              "commandname": "project",
              "dotnetrunmessages": "true",
              "launchbrowser": true,
              "launchurl": "weatherforecast",
              "applicationurl": "http://localhost:5000"
            }
          }
        }

        配置完成后,就可以在vs上方工具栏中的项目启动处选择启动项了

        基于环境的 startup

        startup类支持针对不同环境进行个性化配置,有三种方式:

        • 1.将iwebhostenvironment注入 startup 类
        • 2.startup 方法约定
        • 3.startup 类约定

        1.将iwebhostenvironment注入 startup 类

        通过将iwebhostenvironment注入 startup 类,然后在方法中使用条件判断书写不同环境下的代码。该方式适用于多环境下,代码差异较少的情况。

        public class startup
        {
            public startup(iconfiguration configuration, iwebhostenvironment webhostenvironment)
            {
                configuration = configuration;
                webhostenvironment = webhostenvironment;
            }
        
            public iconfiguration configuration { get; }
        
            public iwebhostenvironment webhostenvironment { get; }
        
            public void configureservices(iservicecollection services)
            {
                if (webhostenvironment.isdevelopment())
                {
                    console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.istest())
                {
                    console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.isstaging())
                {
                    console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.isproduction())
                {
                    console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
                }
            }
        
            public void configure(iapplicationbuilder app)
            {
                if (webhostenvironment.isdevelopment())
                {
                    console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.istest())
                {
                    console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.isstaging())
                {
                    console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
                }
                else if (webhostenvironment.isproduction())
                {
                    console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
                }
            }
        }
        
        public static class apphostenvironmentenvextensions
        {
            public static bool istest(this ihostenvironment hostenvironment)
            {
                if (hostenvironment == null)
                {
                    throw new argumentnullexception(nameof(hostenvironment));
                }
        
                return hostenvironment.isenvironment(appenvironments.test);
            }
        }
        
        public static class appenvironments
        {
            public static readonly string test = nameof(test);
        }

        2.startup 方法约定

        上面的方式把不同环境的代码放在了同一个方法中,看起来比较混乱也不容易区分。因此我们希望configureservicesconfigure能够根据不同的环境进行代码拆分。

        我们可以通过方法命名约定来解决,约定configure{environmentname}servicesconfigure{environmentname}services来装载不同环境的代码。如果当前环境没有对应的方法,则使用原来的configureservicesconfigure方法。

        我就只拿 development 和 production 举例了

        public class startup
        {
            // 我这里注入 iwebhostenvironment,仅仅是为了打印出来当前环境信息
            public startup(iconfiguration configuration, iwebhostenvironment webhostenvironment)
            {
                configuration = configuration;
                webhostenvironment = webhostenvironment;
            }
        
            public iconfiguration configuration { get; }
        
            public iwebhostenvironment webhostenvironment { get; }
        
            #region configureservices
            private void startupconfigureservices(iservicecollection services)
            {
                console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
            }
        
            public void configuredevelopmentservices(iservicecollection services)
            {
                startupconfigureservices(services);
            }
        
            public void configureproductionservices(iservicecollection services)
            {
                startupconfigureservices(services);
            }
        
            public void configureservices(iservicecollection services)
            {
                startupconfigureservices(services);
            }
            #endregion
        
            #region configure
            private void startupconfigure(iapplicationbuilder app)
            {
                console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
            }
        
            public void configuredevelopment(iapplicationbuilder app)
            {
                startupconfigure(app);
            }
        
            public void configureproduction(iapplicationbuilder app)
            {
                startupconfigure(app);
            }
        
            public void configure(iapplicationbuilder app)
            {
                startupconfigure(app);
            } 
            #endregion
        }

        3.startup 类约定

        该方式适用于多环境下,代码差异较大的情况。

        程序启动时,会优先寻找当前环境命名符合startup{environmentname}的 startup 类,如果找不到,则使用名称为startup的类

        首先,createhostbuilder方法需要做一处修改

        public static ihostbuilder createhostbuilder(string[] args) =>
            host.createdefaultbuilder(args)
                .configurewebhostdefaults(webbuilder =>
                {
                    //webbuilder.usestartup<startup>();
        
                    webbuilder.usestartup(typeof(startup).gettypeinfo().assembly.fullname);
                });

        接下来,就是为各个环境定义 startup 类了(我就只拿 development 和 production 举例了)

        public class startupdevelopment
        {
            // 我这里注入 iwebhostenvironment,仅仅是为了打印出来当前环境信息
            public startupdevelopment(iconfiguration configuration, iwebhostenvironment webhostenvironment)
            {
                configuration = configuration;
                webhostenvironment = webhostenvironment;
            }
        
            public iconfiguration configuration { get; }
        
            public iwebhostenvironment webhostenvironment { get; }
        
            public void configureservices(iservicecollection services)
            {
                console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
            }
        
            public void configure(iapplicationbuilder app)
            {
                console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
            }
        }
        
        public class startupproduction
        {
            public startupproduction(iconfiguration configuration, iwebhostenvironment webhostenvironment)
            {
                configuration = configuration;
                webhostenvironment = webhostenvironment;
            }
        
            public iconfiguration configuration { get; }
        
            public iwebhostenvironment webhostenvironment { get; }
        
            public void configureservices(iservicecollection services)
            {
                console.writeline($"{nameof(configureservices)}: {webhostenvironment.environmentname}");
            }
        
            public void configure(iapplicationbuilder app)
            {
                console.writeline($"{nameof(configure)}: {webhostenvironment.environmentname}");
            }
        }

        到此这篇关于理解asp.net core 启动类(startup)的文章就介绍到这了,更多相关asp.net core startup内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!