ABP VNext 系列:框架启动流程以及依赖注入原理和源码分析

ABP VNext 系列:框架启动流程以及依赖注入原理和源码分析

简单介绍 ABP VNext

Github 地址:https://github.com/abpframework/abp

官网文档地址:https://abp.io/docs/latest

官网:https://abp.io/

ABP VNext 框架是一个基于 ASP.NET Core 的完整基础架构,也就是我们现在称的 ABP 框架,它遵循软件开发最佳实践和最新技术来创建现代 Web 应用程序和 API。

ABP vNext 是 ABP 框架作者所发起的新项目,ABP vNext 框架核心库比 ABP 框架更加精简,因为将原有许多的组件从其核心库抽离成独立的组件。这样开发人员可以更加灵活的选择自己需要的功能进行集成,使项目远离臃肿的库。

ABP 提供了基于领域驱动设计原则和模式的完整、模块化和分层的软件架构。它还提供了实现此架构所需的基础架构和指导。

ABP 框架提供了许多功能来更轻松地实现实际场景,例如事件总线、后台作业系统、审计日志、BLOB 存储、数据播种、数据过滤等。

框架启动流程

模块化文档:https://abp.io/docs/latest/framework/architecture/modularity/basics

下载源码:https://github.com/abpframework/abp/tree/dev/framework

参考构建一个新 ASP.NET Core MVC 的项目:https://abp.io/docs/latest/get-started/empty-aspnet-core-application

public class Program

{

public static async Task Main(string[] args)

{

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseAutofac(); //推荐使用Autofac作为依赖注入框架

//注入ABP相关服务

await builder.Services.AddApplicationAsync();

var app = builder.Build();

//初始化程序

await app.InitializeApplicationAsync();

await app.RunAsync();

}

}

///

/// 需要什么模块引入什么模块,才会注入

///

[DependsOn(

typeof(AbpAspNetCoreMvcModule),

typeof(AbpSwashbuckleModule),

typeof(MCodeAbpApplicationModule),

typeof(AbpAutofacModule)

)]

public class MCodeAbpWebModule : AbpModule

{

///

/// 将你的服务添加到依赖注入系统并配置其他模块的

///

///

///

public override Task ConfigureServicesAsync(ServiceConfigurationContext context)

{

var service = context.Services;

//设置api格式

service.AddControllers();

//动态Api

Configure(options =>

{

options.ConventionalControllers.Create(typeof(MCodeAbpApplicationModule).Assembly, options => options.RemoteServiceName = "v1");

//统一前缀

options.ConventionalControllers.ConventionalControllerSettings.ForEach(x => x.RootPath = "api/app");

});

///配置 Swagger

service.AddAbpSwaggerGen(options =>

{

var serviceProvider = service.BuildServiceProvider();

var mvcOptions = serviceProvider.GetRequiredService>();

var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);

options.SwaggerDoc("v1", new OpenApiInfo { Title = "mcode.abp.api", Version = "v1", Description = "mcode.abp.api" });

// 根据分组名称过滤 API 文档(必须) *****

options.DocInclusionPredicate((docName, apiDesc) =>

{

if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)

{

var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();

if (settingOrNull is not null)

{

return docName == settingOrNull.RemoteServiceName;

}

}

return false;

});

//配置模型标识,默认type.Name,名称一样,不同明明空间会报错,所以改成FullName,加上命名空间区分

options.CustomSchemaIds(type => type.FullName);

});

return Task.CompletedTask;

}

///

/// 在应用程序启动时执行代码

///

///

///

public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)

{

var app = context.GetApplicationBuilder();

app.UseStaticFiles();

app.UseRouting();

app.UseSwagger();

app.UseAbpSwaggerUI(c =>

{

c.SwaggerEndpoint("/swagger/v1/swagger.json", "mcode.abp.api");

});

app.UseConfiguredEndpoints();

return Task.CompletedTask;

}

}

public class MCodeAbpApplicationModule : AbpModule

{

}

///

///扩展示例

///

[Route("[controller]/[action]")]

public class TestService : ApplicationService

{

///

/// 动态Api

///

///

///

[HttpGet]

public string GetHelloWorld(string? name)

{

return name ?? "HelloWord";

}

}

源码分析

替换 IOC 容器

通过 UseServiceProviderFactory() 方法 ,构建 Abp 自定义的 Autofac 容器工厂,实际上就是做了一层包装

public static class AbpAutofacHostBuilderExtensions

{

public static IHostBuilder UseAutofac(this IHostBuilder hostBuilder)

{

//初始化AutoFac容器构建对象

var containerBuilder = new ContainerBuilder();

return hostBuilder.ConfigureServices((_, services) =>

{

services.AddObjectAccessor(containerBuilder);

})

//注册Abp自定义的Autofac容器工厂

.UseServiceProviderFactory(new AbpAutofacServiceProviderFactory(containerBuilder));

}

}

添加 ABP 应用程序

通过 AbpApplicationFactory 类工厂创建启动模块程序

public async static Task AddApplicationAsync(

[NotNull] this IServiceCollection services,

Action? optionsAction = null)

where TStartupModule : IAbpModule

{

return await AbpApplicationFactory.CreateAsync(services, optionsAction);

}

///

/// 通过 AbpApplicationFactory 创建 ABP 应用程序

///

///

///

///

///

public async static Task CreateAsync(

[NotNull] IServiceCollection services,

Action? optionsAction = null)

where TStartupModule : IAbpModule

{

var app = Create(typeof(TStartupModule), services, options =>

{

options.SkipConfigureServices = true;

optionsAction?.Invoke(options);

});

//调用配置服务

await app.ConfigureServicesAsync();

return app;

}

创建 ABP 外部服务提供者

AbpApplicationWithExternalServiceProvider 中构造方法只做了了一件事,就是添加自己到服务中去。

核心是继承了 AbpApplicationBase 基类,实例化后会调用基类的构造方法

///

/// 创建 ABP 外部服务提供者(实例化应用程序基类)

///

///

///

///

///

public static IAbpApplicationWithExternalServiceProvider Create(

[NotNull] Type startupModuleType,

[NotNull] IServiceCollection services,

Action? optionsAction = null)

{

//会实例化ABP应用程序基类

return new AbpApplicationWithExternalServiceProvider(startupModuleType, services, optionsAction);

}

初始化 ABP 程序基类

模块的初始化动作是在 AbpApplicationBase 基类开始的,在该基类当中除了注入模块相关的基础设施以外。

还定义了模块的初始化方法,即 LoadModules() 方法,在该方法内部是调用的 IModuleLoader 去执行具体的加载操作

///

/// 初始化 Abp 程序

///

///

///

///

internal AbpApplicationBase(

[NotNull] Type startupModuleType,

[NotNull] IServiceCollection services,

Action? optionsAction)

{

Check.NotNull(startupModuleType, nameof(startupModuleType));

Check.NotNull(services, nameof(services));

StartupModuleType = startupModuleType;

Services = services;

services.TryAddObjectAccessor();

var options = new AbpApplicationCreationOptions(services);

//将Services添加到配置中去

optionsAction?.Invoke(options);

//获取应用程序名称

ApplicationName = GetApplicationName(options);

//添加当前对象为各种服务实例

services.AddSingleton(this);

services.AddSingleton(this);

services.AddSingleton(this);

//添加环境变量

services.AddSingleton(new AbpHostEnvironment()

{

EnvironmentName = options.Environment

});

//添加日志、选项等

services.AddCoreServices();

//添加核心Abp服务,以及模块的四个应用程序生命周期

services.AddCoreAbpServices(this, options);

//读取所有的模块,并按照预加载、初始化、初始化完成的顺序执行其生命周期方法

Modules = LoadModules(services, options);

//默认为true,不执行

if (!options.SkipConfigureServices)

{

ConfigureServices();

}

}

添加核心 ABP 服务

AddCoreServices() 方法中是添加包括常规的日志、选项、本地化。

AddCoreAbpServices() 方法则是 添加 Abp 的核心服务,包括各种读取器,配置

///

/// 添加核心服务

///

///

internal static void AddCoreServices(this IServiceCollection services)

{

//添加选项

services.AddOptions();

//添加日志

services.AddLogging();

//添加本地化

services.AddLocalization();

}

///

/// 添加核心ABP服务

///

///

///

///

internal static void AddCoreAbpServices(this IServiceCollection services,

IAbpApplication abpApplication,

AbpApplicationCreationOptions applicationCreationOptions)

{

var moduleLoader = new ModuleLoader();

var assemblyFinder = new AssemblyFinder(abpApplication);

var typeFinder = new TypeFinder(assemblyFinder);

if (!services.IsAdded())

{

services.ReplaceConfiguration(

ConfigurationHelper.BuildConfiguration(

applicationCreationOptions.Configuration

)

);

}

//添加各种读取器和查找器

services.TryAddSingleton(moduleLoader);

services.TryAddSingleton(assemblyFinder);

services.TryAddSingleton(typeFinder);

services.TryAddSingleton(new DefaultInitLoggerFactory());

//添加 AbpApplication所在程序集的类型

services.AddAssemblyOf();

services.AddTransient(typeof(ISimpleStateCheckerManager<>), typeof(SimpleStateCheckerManager<>));

//配置生命周期选项

services.Configure(options =>

{

options.Contributors.Add();

options.Contributors.Add();

options.Contributors.Add();

options.Contributors.Add();

});

}

读取模块

进入 IModuleLoader 的默认实现 ModuleLoader,在它的 LoadModules() 方法中,基本逻辑如下:

扫描当前应用程序的所有模块类,并构建模块描述对象。

基于模块描述对象,使用拓扑排序算法来按照模块的依赖性进行排序。

排序完成之后,遍历排序完成的模块描述对象,依次执行它们的三个生命周期方法。

protected virtual IReadOnlyList LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)

{

//通过ModelLoader实例读取模块,LoadModules的时候会将实例添加到容器中

return services

.GetSingletonInstance()

.LoadModules(

services,

StartupModuleType,

options.PlugInSources

);

}

///

/// 读取模块

///

///

///

///

///

public IAbpModuleDescriptor[] LoadModules(

IServiceCollection services,

Type startupModuleType,

PlugInSourceList plugInSources)

{

Check.NotNull(services, nameof(services));

Check.NotNull(startupModuleType, nameof(startupModuleType));

Check.NotNull(plugInSources, nameof(plugInSources));

//获取模块说明

var modules = GetDescriptors(services, startupModuleType, plugInSources);

//排序

modules = SortByDependency(modules, startupModuleType);

return modules.ToArray();

}

///

/// 获取模块说明

///

///

///

///

///

private List GetDescriptors(

IServiceCollection services,

Type startupModuleType,

PlugInSourceList plugInSources)

{

var modules = new List();

//查找Module

FillModules(modules, services, startupModuleType, plugInSources);

//添加到当前模块描述对象的 Dependencies 属性

SetDependencies(modules);

return modules.Cast().ToList();

}

///

/// 查找所有的模块

///

///

///

///

///

protected virtual void FillModules(

List modules,

IServiceCollection services,

Type startupModuleType,

PlugInSourceList plugInSources)

{

var logger = services.GetInitLogger();

//查找到所有的模块

//All modules starting from the startup module

foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType, logger))

{

//创建模块说明并添加到容器中去

modules.Add(CreateModuleDescriptor(services, moduleType));

}

//Plugin modules

foreach (var moduleType in plugInSources.GetAllModules(logger))

{

if (modules.Any(m => m.Type == moduleType))

{

continue;

}

modules.Add(CreateModuleDescriptor(services, moduleType, isLoadedAsPlugIn: true));

}

}

配置服务(注入上下文、执行其生命周期方法)

创建完 Abp 应用程序后会调用 ConfigureServicesAsync() 方法 ,

会执行每个模块中的:ConfigureServices()、PreConfigureServices()、PostConfigureServices() 方法等

///

/// 配置服务

///

///

///

public virtual async Task ConfigureServicesAsync()

{

CheckMultipleConfigureServices();

//将服务配置上下文对象添加到容器中

var context = new ServiceConfigurationContext(Services);

Services.AddSingleton(context);

//遍历所有的容器中的模块,并指定上下文对象

foreach (var module in Modules)

{

if (module.Instance is AbpModule abpModule)

{

abpModule.ServiceConfigurationContext = context;

}

}

//遍历执行模块中PreConfigureServicesAsync方法(ConfigureServices之前执行)

foreach (var module in Modules.Where(m => m.Instance is IPreConfigureServices))

{

try

{

await ((IPreConfigureServices)module.Instance).PreConfigureServicesAsync(context);

}

catch (Exception ex)

{

throw new AbpInitializationException($"An error occurred during {nameof(IPreConfigureServices.PreConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);

}

}

var assemblies = new HashSet();

////遍历模块,将所有的模块中需要注入的进行注入

foreach (var module in Modules)

{

if (module.Instance is AbpModule abpModule)

{

if (!abpModule.SkipAutoServiceRegistration)

{

foreach (var assembly in module.AllAssemblies)

{

if (!assemblies.Contains(assembly))

{

Services.AddAssembly(assembly);

assemblies.Add(assembly);

}

}

}

}

try

{

//执行模块实例中的方法

await module.Instance.ConfigureServicesAsync(context);

}

catch (Exception ex)

{

throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);

}

}

//遍历执行模块中PostConfigureServicesAsync方法(ConfigureServices之后执行)

foreach (var module in Modules.Where(m => m.Instance is IPostConfigureServices))

{

try

{

await ((IPostConfigureServices)module.Instance).PostConfigureServicesAsync(context);

}

catch (Exception ex)

{

throw new AbpInitializationException($"An error occurred during {nameof(IPostConfigureServices.PostConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);

}

}

//遍历所有的容器中的模块,并清空上下文对象

foreach (var module in Modules)

{

if (module.Instance is AbpModule abpModule)

{

abpModule.ServiceConfigurationContext = null!;

}

}

_configuredServices = true;

TryToSetEnvironment(Services);

}

组件自动注册源码分析

ABP 提供了三种接口:ISingletonDependency 和 ITransientDependency 、IScopedDependency 接口的方式进行注入

方便我们的类型/组件自动注册,这三种接口分别对应了对象的 单例、瞬时、范围 生命周期。

只要任何类型/接口实现了以上任意接口,ABP vNext 就会在系统启动时候,将这些对象注册到 IoC 容器当中

也可以通过 DependencyAttribute 特性 去标记服务的类型,指定服务的生命周期

在模块系统调用模块的 ConfigureServicesAsync() 的时候,就会有一个 services.AddAssembly()方法,

他会将模块的所属的程序集传入

//ConfigureServices

foreach (var module in Modules)

{

if (module.Instance is AbpModule abpModule)

{

//是否跳过服务的自动注册,默认为 false

if (!abpModule.SkipAutoServiceRegistration)

{

foreach (var assembly in module.AllAssemblies)

{

if (!assemblies.Contains(assembly))

{

//注入当前程序集

Services.AddAssembly(assembly);

assemblies.Add(assembly);

}

}

}

}

try

{

module.Instance.ConfigureServices(context);

}

catch (Exception ex)

{

throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);

}

}

通过获取规则注册器进行注册,Abp 框架中自带默认的注册器 DefaultConventionalRegistrar ,也可以自定义注册器

通过实现 ConventionalRegistrarBase 抽象类去自定义

public static IServiceCollection AddAssembly(this IServiceCollection services, Assembly assembly)

{

///获得所有注册器,然后调用注册器的 AddAssembly 方法注册类型。

foreach (var registrar in services.GetConventionalRegistrars())

{

registrar.AddAssembly(services, assembly);

}

return services;

}

//抽象基类

public abstract class ConventionalRegistrarBase : IConventionalRegistrar

{

///

///

///

///

///

public virtual void AddAssembly(IServiceCollection services, Assembly assembly)

{

//获得程序集内的所有类型,过滤掉抽象类和泛型类型。

var types = AssemblyHelper

.GetAllTypes(assembly)

.Where(

type => type != null &&

type.IsClass &&

!type.IsAbstract &&

!type.IsGenericType

).ToArray();

AddTypes(services, types);

}

//添加类型

public virtual void AddTypes(IServiceCollection services, params Type[] types)

{

foreach (var type in types)

{

AddType(services, type);

}

}

public abstract void AddType(IServiceCollection services, Type type);

}

通过的默认注册器进行注册

public class DefaultConventionalRegistrar : ConventionalRegistrarBase

{

///

/// 添加类型

///

///

///

public override void AddType(IServiceCollection services, Type type)

{

//如果标记了:DisableConventionalRegistrationAttribute特性的,不进行注入

if (IsConventionalRegistrationDisabled(type))

{

return;

}

//获取Dependency依赖特性

var dependencyAttribute = GetDependencyAttributeOrNull(type);

//获取生命周期

var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);

if (lifeTime == null)

{

return;

}

//转成服务标识

var exposedServiceAndKeyedServiceTypes = GetExposedKeyedServiceTypes(type).Concat(GetExposedServiceTypes(type).Select(t => new ServiceIdentifier(t))).ToList();

//生成服务

TriggerServiceExposing(services, type, exposedServiceAndKeyedServiceTypes);

foreach (var exposedServiceType in exposedServiceAndKeyedServiceTypes)

{

var allExposingServiceTypes = exposedServiceType.ServiceKey == null

? exposedServiceAndKeyedServiceTypes.Where(x => x.ServiceKey == null).ToList()

: exposedServiceAndKeyedServiceTypes.Where(x => x.ServiceKey?.ToString() == exposedServiceType.ServiceKey?.ToString()).ToList();

//创建服务描述

var serviceDescriptor = CreateServiceDescriptor(

type,

exposedServiceType.ServiceKey,

exposedServiceType.ServiceType,

allExposingServiceTypes,

lifeTime.Value

);

if (dependencyAttribute?.ReplaceServices == true)

{

services.Replace(serviceDescriptor);

}

else if (dependencyAttribute?.TryRegister == true)

{

services.TryAdd(serviceDescriptor);

}

else

{

services.Add(serviceDescriptor);

}

}

}

}

从 GetLifeTimeOrNull 方法中 获取生命周期

///

/// 获取生命周期

///

///

///

///

protected virtual ServiceLifetime? GetLifeTimeOrNull(Type type, DependencyAttribute? dependencyAttribute)

{

// 特性中设置了生命周期或者继承了接口

return dependencyAttribute?.Lifetime ?? GetServiceLifetimeFromClassHierarchy(type) ?? GetDefaultLifeTimeOrNull(type);

}

///

/// 从接口中获取生命周期

///

///

///

protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarchy(Type type)

{

if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))

{

return ServiceLifetime.Transient;

}

if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type))

{

return ServiceLifetime.Singleton;

}

if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type))

{

return ServiceLifetime.Scoped;

}

return null;

}

初始化 ABP 应用程序

通过 await app.InitializeApplicationAsync(); 方法会初始化应用程序,然后执行每一个模块中的生命周期的方法

///

/// 异步初始化 ABP 应用程序

///

///

///

public async static Task InitializeApplicationAsync([NotNull] this IApplicationBuilder app)

{

Check.NotNull(app, nameof(app));

app.ApplicationServices.GetRequiredService>().Value = app;

var application = app.ApplicationServices.GetRequiredService();

var applicationLifetime = app.ApplicationServices.GetRequiredService();

//注册程序生命周期停止事件

applicationLifetime.ApplicationStopping.Register(() =>

{

//执行 Abp程序中的 ShutdownAsync 方法, 会调用每个模块中的 ShutdownAsync 方法

AsyncHelper.RunSync(() => application.ShutdownAsync());

});

//释放 Abp 应用

applicationLifetime.ApplicationStopped.Register(() =>

{

application.Dispose();

});

// 执行 初始化 Abp 方法 ,初始化 每个模块,执行每一个模块中的 InitializeAsync 方法

await application.InitializeAsync(app.ApplicationServices);

}

初始化每一个模块生命周期方法

///

/// 设置服务提供者

///

///

protected virtual void SetServiceProvider(IServiceProvider serviceProvider)

{

ServiceProvider = serviceProvider;

ServiceProvider.GetRequiredService>().Value = ServiceProvider;

}

///

/// 初始化模块

///

///

protected virtual async Task InitializeModulesAsync()

{

using (var scope = ServiceProvider.CreateScope())

{

WriteInitLogs(scope.ServiceProvider);

await scope.ServiceProvider

.GetRequiredService() //初始化模块

.InitializeModulesAsync(new ApplicationInitializationContext(scope.ServiceProvider));

}

}

///

/// 初始化模块

///

///

///

///

public virtual async Task InitializeModulesAsync(ApplicationInitializationContext context)

{

// 原先在 AddCoreAbpServices() 方法中 配置的 生命周期,

foreach (var contributor in _lifecycleContributors)

{

foreach (var module in _moduleContainer.Modules)

{

try

{

//执行每个生命周期(加载之前、加载中、加载之后)中的 初始化方法

await contributor.InitializeAsync(context, module.Instance);

}

catch (Exception ex)

{

throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex);

}

}

}

_logger.LogInformation("Initialized all ABP modules.");

}

四个生命周期 OnApplicationInitializationModuleLifecycleContributor 、

OnPreApplicationInitializationModuleLifecycleContributor 、

OnPostApplicationInitializationModuleLifecycleContributor

OnApplicationShutdownModuleLifecycleContributor 中的方法,实际就是调用到具体模块中的生命周期各个方法

public class OnApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase

{

public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)

{

if (module is IOnApplicationInitialization onApplicationInitialization)

{

await onApplicationInitialization.OnApplicationInitializationAsync(context);

}

}

public override void Initialize(ApplicationInitializationContext context, IAbpModule module)

{

(module as IOnApplicationInitialization)?.OnApplicationInitialization(context);

}

}

public class OnApplicationShutdownModuleLifecycleContributor : ModuleLifecycleContributorBase

{

public async override Task ShutdownAsync(ApplicationShutdownContext context, IAbpModule module)

{

if (module is IOnApplicationShutdown onApplicationShutdown)

{

await onApplicationShutdown.OnApplicationShutdownAsync(context);

}

}

public override void Shutdown(ApplicationShutdownContext context, IAbpModule module)

{

(module as IOnApplicationShutdown)?.OnApplicationShutdown(context);

}

}

public class OnPreApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase

{

public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)

{

if (module is IOnPreApplicationInitialization onPreApplicationInitialization)

{

await onPreApplicationInitialization.OnPreApplicationInitializationAsync(context);

}

}

public override void Initialize(ApplicationInitializationContext context, IAbpModule module)

{

(module as IOnPreApplicationInitialization)?.OnPreApplicationInitialization(context);

}

}

public class OnPostApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase

{

public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)

{

if (module is IOnPostApplicationInitialization onPostApplicationInitialization)

{

await onPostApplicationInitialization.OnPostApplicationInitializationAsync(context);

}

}

public override void Initialize(ApplicationInitializationContext context, IAbpModule module)

{

(module as IOnPostApplicationInitialization)?.OnPostApplicationInitialization(context);

}

}

相关推荐

《暗黑3》执政官8分钟横扫双塔可获得300万经验
365bet在线娱乐场

《暗黑3》执政官8分钟横扫双塔可获得300万经验

📅 02-02 👁️ 2085
如何训练仓鼠,掌握这些技巧让你的宠物更聪明
英国beat365官方登录

如何训练仓鼠,掌握这些技巧让你的宠物更聪明

📅 07-23 👁️ 4113
FIBA竟称“中国男篮从日本偷走胜利”,评论区翻车
365bet在线娱乐场

FIBA竟称“中国男篮从日本偷走胜利”,评论区翻车

📅 06-23 👁️ 3124