在總結完整個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類中定義了兩個主要的方法:ConfigureServices和Configure方法,這兩個方法是從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系列的主目錄。