ABP中的模块初始化过程

Posted seekdream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ABP中的模块初始化过程相关的知识,希望对你有一定的参考价值。

  在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp.Net Core项目中,我们启动一个带Swagger UI的Web API项目为例,在介绍这个Web API项目之前我们先来看看整个Swagger 文档的样式。

技术分享图片

  我们定义的WebAPI最终都会以Swagger文档这种形式来展现出来,通过这种形式也是非常方便我们进行代码的调试的,在进行网站的前后端分离开发的过程中,前端去定义接口后端根据前端定义的接口进行开发,这个模式能够实现整个开发的分离,当然这篇文章主要不是介绍如何去进行前后端分离开发而是重点介绍如何ABP模块中代码的加载顺序,前面的截图是整个ABP项目的启动界面,通过这些能够让我们对整个项目有一个概念性的认识和理解。

  在整个项目的运行过程中,首先也是从Program类中开始的,首先执行Program类中的静态Main方法,然后在Main方法中会创建一个IWebHost对象,然后执行Run方法,看起来像下面的形式:

 public class Program
    {
        private static IConfiguration Configuration { get; set; }

        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args)
        {
            return WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
        }
    }

  在这里面会执行UseStartup<Startup>()这个方法,然后会将控制主逻辑转移到Startup这个类中,下面我们再来看一看Startup这个类中执行了些什么操作?

 public class Startup
    {
        private const string _defaultCorsPolicyName = "localhost";

        private readonly IConfigurationRoot _appConfiguration;

        public Startup(IHostingEnvironment env)
        {
            _appConfiguration = env.GetAppConfiguration();
        }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            // MVC
            services.AddMvc(
                options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
            );

            IdentityRegistrar.Register(services);
            AuthConfigurer.Configure(services, _appConfiguration);

#if FEATURE_SIGNALR_ASPNETCORE
            services.AddSignalR();
#endif

            // Configure CORS for angular2 UI
            services.AddCors(
                options => options.AddPolicy(
                    _defaultCorsPolicyName,
                    builder => builder
                        .WithOrigins(
                            // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
                            _appConfiguration["App:CorsOrigins"]
                                .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.RemovePostFix("/"))
                                .ToArray()
                        )
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                )
            );

            // Swagger - Enable this line and the related lines in Configure method to enable swagger UI
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info { Title = "Server API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);

                // Define the BearerAuth scheme that‘s in use
                options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
                {
                    Description = "JWT Authorization header using the Bearer scheme. Example: "Authorization: Bearer {token}"",
                    Name = "Authorization",
                    In = "header",
                    Type = "apiKey"
                });
                // Assign scope requirements to operations based on AuthorizeAttribute
                options.OperationFilter<SecurityRequirementsOperationFilter>();
            });

            // Configure Abp and Dependency Injection
            return services.AddAbp<ServerWebHostModule>(
                // Configure Log4Net logging
                options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                    f => f.UseAbpLog4Net().WithConfig("log4net.config")
                )
            );
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.

            app.UseCors(_defaultCorsPolicyName); // Enable CORS!

            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseAbpRequestLocalization();

#if FEATURE_SIGNALR
            // Integrate with OWIN
            app.UseAppBuilder(ConfigureOwinServices);
#elif FEATURE_SIGNALR_ASPNETCORE
            app.UseSignalR(routes =>
            {
                routes.MapHub<AbpCommonHub>("/signalr");
            });
#endif

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "defaultWithArea",
                    template: "{area}/{controller=Home}/{action=Index}/{id?}");

                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            // Enable middleware to serve generated Swagger as a JSON endpoint
            app.UseSwagger();
            // Enable middleware to serve swagger-ui assets (html, JS, CSS etc.)
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "Server API V1");
                options.IndexStream = () => Assembly.GetExecutingAssembly()
                    .GetManifestResourceStream("SunLight.Server.Web.Host.wwwroot.swagger.ui.index.html");
            }); // URL: /swagger
        }

#if FEATURE_SIGNALR
        private static void ConfigureOwinServices(IAppBuilder app)
        {
            app.Properties["host.AppName"] = "Server";

            app.UseAbp();
            
            app.Map("/signalr", map =>
            {
                map.UseCors(CorsOptions.AllowAll);
                var hubConfiguration = new HubConfiguration
                {
                    EnableJSONP = true
                };
                map.RunSignalR(hubConfiguration);
            });
        }
#endif
    }

  上面的过程熟悉Asp.Net Core开发的都应该十分熟悉,在Startup类中定义了两个主要的方法:ConfigureServicesConfigure方法,这两个方法是从ConfigureServices开始进行服务配置,包括MVC配置、CORS配置,Swagger的一些配置以及最关键的ABP的配置,这里仅仅列出最为关键的过程,然后对着这些代码来一步步进行分析。

return services.AddAbp<ServerWebHostModule>(
                // Configure Log4Net logging
                options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                    f => f.UseAbpLog4Net().WithConfig("log4net.config")
                )
            );

  这里面最为关键的就是执行AddAbp方法了,整个ABP框架的执行也将从这里拉开序幕,我们来看看ABP项目的源码

public static class AbpServiceCollectionExtensions
    {
        /// <summary>
        /// Integrates ABP to AspNet Core.
        /// </summary>
        /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
        /// <param name="services">Services.</param>
        /// <param name="optionsAction">An action to get/modify options</param>
        public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
            where TStartupModule : AbpModule
        {
            var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);

            ConfigureAspNetCore(services, abpBootstrapper.IocManager);

            return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
        }

        private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
        {
            //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
            
            //Use DI to create controllers
            services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

            //Use DI to create view components
            services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());

            //Change anti forgery filters (to work proper with non-browser clients)
            services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
            services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());

            //Add feature providers
            var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
            partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));

            //Configure JSON serializer
            services.Configure<MvcJsonOptions>(jsonOptions =>
            {
                jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
                {
                    NamingStrategy = new CamelCaseNamingStrategy()
                };
            });

            //Configure MVC
            services.Configure<MvcOptions>(mvcOptions =>
            {
                mvcOptions.AddAbp(services);
            });

            //Configure Razor
            services.Insert(0,
                ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
                    new ConfigureOptions<RazorViewEngineOptions>(
                        (options) =>
                        {
                            options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
                        }
                    )
                )
            );
        }

        private static AbpBootstrapper AddAbpBootstrapper<TStartupModule>(IServiceCollection services, Action<AbpBootstrapperOptions> optionsAction)
            where TStartupModule : AbpModule
        {
            var abpBootstrapper = AbpBootstrapper.Create<TStartupModule>(optionsAction);
            services.AddSingleton(abpBootstrapper);
            return abpBootstrapper;
        }
    }

  整个ABP项目在AbpServiceCollectionExtensions这个类里面定义了一个AddABP的方法,就像当前方法的注释写的那样,让ABP项目与Asp.Net Core结合,在这个方法中首先就是创建唯一的AbpBootstrapper的实例,在这里创建的方式采用的是静态方法Create方法,下面通过源代码来分析一下这个方法。

 /// <summary>
        /// Creates a new <see cref="AbpBootstrapper"/> instance.
        /// </summary>
        /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
        /// <param name="optionsAction">An action to set options</param>
        public static AbpBootstrapper Create<TStartupModule>([CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
            where TStartupModule : AbpModule
        {
            return new AbpBootstrapper(typeof(TStartupModule), optionsAction);
        }

  这个方法是一个泛型方法,用于创建一个唯一的AbpBootstrapper的实例,这里的泛型参数是TStartupModule,这个是整个项目的启动的Module,一般是XXXWebHostModule,后面的参数是一个参数类型为AbpBootstrapperOptions的Action类型委托,这个类型是一个可为空类型。

  接下来我们再看看在私有的AbpBootstrapper构造函数中做了哪些事情,然后来一步步分析,首先来看看源代码。

 /// <summary>
        /// Creates a new <see cref="AbpBootstrapper"/> instance.
        /// </summary>
        /// <param name="startupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</param>
        /// <param name="optionsAction">An action to set options</param>
        private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
        {
            Check.NotNull(startupModule, nameof(startupModule));

            var options = new AbpBootstrapperOptions();
            optionsAction?.Invoke(options);

            if (!typeof(AbpModule).GetTypeInfo().IsAssignableFrom(startupModule))
            {
                throw new ArgumentException($"{nameof(startupModule)} should be derived from {nameof(AbpModule)}.");
            }

            StartupModule = startupModule;

            IocManager = options.IocManager;
            PlugInSources = options.PlugInSources;

            _logger = NullLogger.Instance;

            if (!options.DisableAllInterceptors)
            {
                AddInterceptorRegistrars();
            }
        }

  首先Check.NotNull是一个静态方法,用于判断整个ABP项目中启动Module是否为Null,如果为Null则直接抛出异常,然后第一步就是创建AbpBootstrapperOptions,在这个options里面定义了整个ABP项目中唯一的依赖注入容器IocManager ,这个容器是通过 IocManager = Abp.Dependency.IocManager.Instance,来完成的,这里我们就来简单看一下我们用的唯一的一个依赖注入容器。

 /// <summary>
        /// Creates a new <see cref="IocManager"/> object.
        /// Normally, you don‘t directly instantiate an <see cref="IocManager"/>.
        /// This may be useful for test purposes.
        /// </summary>
        public IocManager()
        {
            IocContainer = new WindsorContainer();
            _conventionalRegistrars = new List<IConventionalDependencyRegistrar>();

            //Register self!
            IocContainer.Register(
                Component.For<IocManager, IIocManager, IIocRegistrar, IIocResolver>().UsingFactoryMethod(() => this)
                );
        }

  在我们的项目中,我们使用的是WindsorContainer,这个老牌的依赖注入容器作为全局的唯一的一个依赖注入容器,关于Castel Windsor这个著名的开源的依赖注入容器我们可以去它的官网去了解其详细信息,请点击这里访问Castle Project项目。

  在使用这个依赖注入容器之前首先要将this也就是自己作为第一个实例注入到WindsorContainer容器中去,关于这个容器还有很多的内容,这个需要我们查看源码查看具体的实现,这个IocContainer类中还有很多的关于注册外部的实例到容器的方法,这个在后续的内容中会逐步去分析。

      另外在AbpBootstrapperOptions这个类的构造函数中除了创建整个ABP项目中唯一的依赖注入容器IocManager以外,还定义了一个PlugInSources的公共属性,这个主要是为构建插件化、模块化项目提供插件模块的一个程序集集合,关于这个部分这里来看一下有哪些内容?

 public class PlugInSourceList : List<IPlugInSource>
    {
        public List<Assembly> GetAllAssemblies()
        {
            return this
                .SelectMany(pluginSource => pluginSource.GetAssemblies())
                .Distinct()
                .ToList();
        }

        public List<Type> GetAllModules()
        {
            return this
                .SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies())
                .Distinct()
                .ToList();
        }
    }

  这个主要是一个为了加载外部的一些继承自AbpModule的一些程序集,包括一些外部文件夹里面的一些可扩展的程序集,我们来看一下ABP中为我们实现了哪些类型的扩展。1 FolderPlugInSource、2 PlugInTypeListSource、3 AssemblyFileListPlugInSource。

  在分析完AbpBootstrapper类中AbpBootstrapperOptions的构建后,我们接着来分析AbpBootstrapper构造函数中其它的逻辑,在后面首先判断传入的泛型参数TStartupModule是否是继承自ABP项目中的基类AbpModule,否则的话就会抛出参数的异常。

  后面的一个重点内容就是就是如果没有默认关掉所有的ABP拦截器的话,就会初始化ABP中所有的拦截器,这个是一个很大的内容,在后面我会花一篇文章来专门介绍ABP中的各种拦截器。

private void AddInterceptorRegistrars()
        {
            ValidationInterceptorRegistrar.Initialize(IocManager);
            AuditingInterceptorRegistrar.Initialize(IocManager);
            EntityHistoryInterceptorRegistrar.Initialize(IocManager);
            UnitOfWorkRegistrar.Initialize(IocManager);
            AuthorizationInterceptorRegistrar.Initialize(IocManager);
        }

  这里暂时先不去分析这些内容,只是让对这个框架先有一个整体上的把握。在完成所有的AbpBootstrapper类的初始化,后面就是执行ConfigureAspNetCore这个方法了,这个方法主要是用于配置一些常用的服务,下面我们通过具体的代码来一步步去分析。

        private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
        {
            //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
            
            //Use DI to create controllers
            services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

            //Use DI to create view components
            services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());

            //Change anti forgery filters (to work proper with non-browser clients)
            services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
            services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());

            //Add feature providers
            var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
            partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));

            //Configure JSON serializer
            services.Configure<MvcJsonOptions>(jsonOptions =>
            {
                jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
                {
                    NamingStrategy = new CamelCaseNamingStrategy()
                };
            });

            //Configure MVC
            services.Configure<MvcOptions>(mvcOptions =>
            {
                mvcOptions.AddAbp(services);
            });

            //Configure Razor
            services.Insert(0,
                ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
                    new ConfigureOptions<RazorViewEngineOptions>(
                        (options) =>
                        {
                            options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
                        }
                    )
                )
            );
        }

  这里面主要是配置一些核心的Asp.Net Core服务,比如用ServiceBasedControllerActivator来替换默认的DefaultControllerActivator ,

使用ServiceBasedViewComponentActivator来替换默认的DefaultViewComponentActivator,这里面我们重点来关注一下services.Configure<MvcOptions>这个方法,我们来看一下最终在里面做了些什么。

internal static class AbpMvcOptionsExtensions
    {
        public static void AddAbp(this MvcOptions options, IServiceCollection services)
        {
            AddConventions(options, services);
            AddFilters(options);
            AddModelBinders(options);
        }

        private static void AddConventions(MvcOptions options, IServiceCollection services)
        {
            options.Conventions.Add(new AbpAppServiceConvention(services));
        }

        private static void AddFilters(MvcOptions options)
        {
            options.Filters.AddService(typeof(AbpAuthorizationFilter));
            options.Filters.AddService(typeof(AbpAuditActionFilter));
            options.Filters.AddService(typeof(AbpValidationActionFilter));
            options.Filters.AddService(typeof(AbpUowActionFilter));
            options.Filters.AddService(typeof(AbpExceptionFilter));
            options.Filters.AddService(typeof(AbpResultFilter));
        }

        private static void AddModelBinders(MvcOptions options)
        {
            options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());
        }
    }

  在这个方法中,主要实现了三个部分:AddConventions、AddFilters、AddModelBinders这三个方法,第一个就是添加默认的协定、第二个就是我们的Asp.Net Core服务中添加各种过滤器,这些过滤器会添加到Asp.Net Core请求过程中,这些Filter的主要作用是在Action执行前和执行后进行一些加工处理,关于Asp.Net Core中的Filter请参考下面的这篇文章第三个部分就是为默认的MvcOptions中添加默认的ModelBinder。

  在AddAbp方法最后执行的是 WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services)这个方这个最后的方法就是替换掉了 Asp.Net Core 默认的 Ioc 容器,最终使用的是 CastleWindsor 的IocContainer。通过这上面的这些过程完成了整个Asp.Net Core 的服务配置过程,在后面的一片文章中我们将重点分析在StarpUp类中Configure方法中调用UseAbp()方法了,这个方法将会去一步步分析整个Module是如何查找,如何加载如何运行的。

  最后,点击这里返回整个ABP系列的主目录。

 

以上是关于ABP中的模块初始化过程的主要内容,如果未能解决你的问题,请参考以下文章

ABP源码分析五:ABP初始化过程

ABP系统设置

abp v2.9.0 Blogging模块安装遇到的问题及解决方法

abp模块生命周期设计思路剖析

[Abp 源码分析] 模块系统

ABP源码分析四十七:ABP中的异常处理