ASP.NET Core before and after the introduction of modular end separation framework for rapid development 4, modular realization of ideas

Source

GitHub:https://github.com/iamoldli/NetModular

Demo Address

Address: http://129.211.40.240:6220
account: admin
password: admin

Front-end framework Demo (provisional)

Address: http://progqx5cu.bkt.clouddn.com/skins/index.html#/
account: admin
password: admin

table of Contents

1, opening
2, to quickly create a service module
3, a data access module introduces
4, modular realization of ideas

Obtain official source

For easy viewing source code, let's get the next official source

Download AspNetCoreSource

git clone --recursive https://github.com/aspnet/AspNetCore

Download ExtensionsSource

git clone https://github.com/aspnet/Extensions.git

Loading mechanism ASP.NET Core Controller

Reference Document: application member in ASP.NET Core

In ASP.NET Corethe application by means ApplicationPartto discover the controller, view of the assembly, or the like numerals MVC helper functions, the application member is made ApplicationPartManagerto the management class. When you call AddMvcor AddMvcCorewhen the method to add MVC-related functions, ASP.NET Coreinternal creates ApplicationPartManagerthe instance, and then the entrance to the assembly as a starting point to find all the assembly unofficial package of its dependency tree, and added to its ApplicationPartsproperty, and finally ApplicationPartManagerthe examples of injection into the container in a single embodiment mode. Here is the relevant source code:

Source Path: AspNetCore \ src \ Mvc.Core \ src \ DependencyInjection \ MvcCoreServiceCollectionExtensions.cs

public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    var partManager = GetApplicationPartManager(services);
    //单例模式注入ApplicationPartManager
    services.TryAddSingleton(partManager);

    ConfigureDefaultFeatureProviders(partManager);
    ConfigureDefaultServices(services);
    AddMvcCoreServices(services);

    var builder = new MvcCoreBuilder(services, partManager);

    return builder;
}

//获取ApplicationPartManager实例
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
    var manager = GetServiceFromCollection<ApplicationPartManager>(services);
    if (manager == null)
    {
        manager = new ApplicationPartManager();

        var environment = GetServiceFromCollection<IHostingEnvironment>(services);
        var entryAssemblyName = environment?.ApplicationName;
        if (string.IsNullOrEmpty(entryAssemblyName))
        {
            return manager;
        }

       
        manager.PopulateDefaultParts(entryAssemblyName);
    }

    return manager;
}

Source Path: AspNetCore \ src \ Mvc.Core \ src \ ApplicationParts \ ApplicationPartManager.cs

internal void PopulateDefaultParts(string entryAssemblyName)
{
    var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));
    var assembliesProvider = new ApplicationAssembliesProvider();

    //加载入口程序集的依赖项树中的所有非官方包的依赖程序集
    var applicationAssemblies = assembliesProvider.ResolveAssemblies(entryAssembly);

    foreach (var assembly in applicationAssemblies)
    {
        var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
        foreach (var part in partFactory.GetApplicationParts(assembly))
        {
            ApplicationParts.Add(part);
        }
    }
}

Because all of our modules are installed by nuget package, it will be automatically compiled into the dependency tree, that is, we do not need to manually load the assembly module.

For the assembly at compile time is not referenced, we can manually load the member by application

// create an assembly part from a class's assembly
var assembly = typeof(Startup).GetTypeInfo().Assembly;
services.AddMvc()
    .AddApplicationPart(assembly);

// OR
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
services.AddMvc()
    .ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));

Loading mechanism module

NetModularThe rule is that when a project starts, look under the root directory of the program modulesdirectory, the directory information is saved exclusively for all modules, and its structure is as follows:

Structure chart

modulesEach represents a subdirectory of the module, which has a subdirectory for each module.jsonfile that is used to describe the module information, which structure is as follows:

{"Id": "Admin","Name":"权限管理","Version":"1.0.0"}
  • Note: module.jsonthe file is automatically generated and packaged into a bag while Nuget module compiled automatically when the installation module included in the project. This uses MSBuild , we are interested can look at. *

The following is to generate module.jsonthe configuration information corresponding to the file

<Project>

  <PropertyGroup>
    <ModulesDir>modules\$(Id)</ModulesDir>
    <ModuleInfo>{"Id": "$(Id)","Name":"$(Name)","Version":"$(Version)"}</ModuleInfo>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="$(ModulesDir)\**">
      <Pack>true</Pack>
      <PackagePath>contentFiles\any\any\$(ModulesDir)</PackagePath>
      <PackageCopyToOutput>true</PackageCopyToOutput>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <TargetPath>Modules\$(Id)\%(RecursiveDir)%(FileName)%(Extension)</TargetPath>
    </Content>
  </ItemGroup>

  <Target Name="ModulesBuildBefore" AfterTargets="Build">

    <!--创建modules目录-->
    <MakeDir Directories="$(ModulesDir)"/>

    <!--生成module.json文件,Note:项目需要生成两次,否则Nuget包中的文件不是最新的-->
    <WriteLinesToFile File="$(ModulesDir)\module.json" Overwrite="true" Lines="$(ModuleInfo)" />
    
  </Target>

</Project>

NetModularIt defines a module information described ModuleInfo.csclass information and a storage module IModuleCollection.csinterfaces

/// <summary>
/// 模块信息
/// </summary>
public class ModuleInfo
{
    /// <summary>
    /// 编号
    /// </summary>
    public string Id { get; set; }

    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 版本
    /// </summary>
    public string Version { get; set; }

    /// <summary>
    /// 模块初始化器
    /// </summary>
    public IModuleInitializer Initializer { get; set; }

    /// <summary>
    /// 程序集信息
    /// </summary>
    public ModuleAssembliesInfo AssembliesInfo { get; set; }
}

/// <summary>
/// 模块集合
/// </summary>
public interface IModuleCollection : IList<ModuleInfo>
{

}

IModuleCollectionHave implemented a class ModuleCollection.cs, is loaded module list operation performed in the constructor of the class:

public ModuleCollection()
{
    var moduleJsonFiles = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "modules"), "module.json", SearchOption.AllDirectories);

    foreach (var file in moduleJsonFiles)
    {
        var moduleInfo = JsonConvert.DeserializeObject<ModuleInfo>(File.ReadAllText(file));
        if (moduleInfo != null)
        {
            //判断是否已存在
            if (_moduleInfos.Any(m => m.Name.Equals(moduleInfo.Name)))
                continue;
            var assemblyHelper = new AssemblyHelper();
            //此处默认模块命名空间前缀与当前项目相同
            moduleInfo.AssembliesInfo = new ModuleAssembliesInfo
            {
                Domain = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Domain")).FirstOrDefault(),
                Infrastructure = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Infrastructure")).FirstOrDefault(),
                Application = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Application")).FirstOrDefault(),
                Web = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Web")).FirstOrDefault(),
            };

            Check.NotNull(moduleInfo.AssembliesInfo.Domain, moduleInfo.Id + "模块的Domain程序集未发现");
            Check.NotNull(moduleInfo.AssembliesInfo.Infrastructure, moduleInfo.Id + "模块的Infrastructure程序集未发现");
            Check.NotNull(moduleInfo.AssembliesInfo.Application, moduleInfo.Id + "模块的Application程序集未发现");
            Check.NotNull(moduleInfo.AssembliesInfo.Web, moduleInfo.Id + "模块的Web程序集未发现");

            //加载模块初始化器
            var moduleInitializerType = moduleInfo.AssembliesInfo.Web.GetTypes().FirstOrDefault(t => typeof(IModuleInitializer).IsAssignableFrom(t));
            if (moduleInitializerType != null && (moduleInitializerType != typeof(IModuleInitializer)))
            {
                moduleInfo.Initializer = (IModuleInitializer)Activator.CreateInstance(moduleInitializerType);
            }

            Add(moduleInfo);
        }
    }
}

When starting the project, create a first ModuleCollectioninstance, in its constructor loads all module information, then using a single injection mode embodiment, so that the module can always access the information in the system.

/// <summary>
/// 添加模块
/// </summary>
/// <param name="services"></param>
/// <param name="env"></param>
/// <returns></returns>
public static IModuleCollection AddModules(this IServiceCollection services, IHostingEnvironment env)
{
    //创建模块集合对象
    var modules = new ModuleCollection();
    services.AddSingleton<IModuleCollection>(modules);

    var cfgHelper = new ConfigurationHelper();
    var cfg = cfgHelper.Load("module", env.EnvironmentName, true);

    //通用配置
    services.Configure<ModuleCommonOptions>(cfg);

    foreach (var module in modules)
    {
        if (module == null)
            continue;

        services.AddApplicationServices(module);

        if (module.Initializer != null)
        {
            module.Initializer.ConfigureServices(services);

            module.Initializer.ConfigOptions(services, cfg.GetSection(module.Id));

            services.AddSingleton(module);
        }
    }

    return modules;
}

And middleware dependency injection processing module

A look at what information is contained in the module:

Structure chart

Module injected into two categories:

1, agreed

Each module has configuration items (Options), the entity (Entity), storage (Repository), database context (DbContext), a unit of work (UnitOfWork), Service (Service), they are a good agreement, including naming, directory , usage, etc., so users only need to follow the rules to use, do not need care inject things, they are in the system automatically injected.

In an example of data access, storage access related data (the Repository), the context database (the DbContext), a unit of work (the UnitOfWork) is performed automatically based on the configuration information and the injection module, and are in a Scopedmanner injection. Specific code to view the Data.AspNetCoreproject.

2, custom

Each module may be some need to inject a unique service, these services are part of a custom requires developers to manually inject. For example, 权限管理(Admin)the module 权限验证处理(PermissionValidateHandler.cs), which implements IPermissionValidateHandlerthe interface, designed to do permission validation.

In addition to injection, each intermediate module has a unique and special configuration for some features, in order to integrate this information along to the project, NetModularabstract one IModuleInitializerinterface, which includes the following four methods:

/// <summary>
/// 模块初始化器接口
/// </summary>
public interface IModuleInitializer
{
    /// <summary>
    /// 注入服务
    /// </summary>
    /// <param name="services"></param>
    void ConfigureServices(IServiceCollection services);

    /// <summary>
    /// 配置中间件
    /// </summary>
    /// <param name="app"></param>
    /// <param name="env"></param>
    void Configure(IApplicationBuilder app, IHostingEnvironment env);

    /// <summary>
    /// 配置MVC
    /// </summary>
    /// <param name="mvcOptions"></param>
    void ConfigureMvc(MvcOptions mvcOptions);

    /// <summary>
    /// 配置选项
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    void ConfigOptions(IServiceCollection services, IConfiguration configuration);
}

Method Description:

1, ConfigureServices: for injecting Service

2, Configure: for configuring middleware

3, ConfigureMvc: used to configure the MVC-related functions

4, ConfigOptions: a configuration module for configuration items

In each module, it must include a IModuleInitializerrealization ModuleInitializerhas 权限管理(Admin)module, for example:

public class ModuleInitializer : IModuleInitializer
{
    public void ConfigureServices(IServiceCollection services)
    {
        //权限验证服务
        services.AddScoped<IPermissionValidateHandler, PermissionValidateHandler>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
    }

    public void ConfigureMvc(MvcOptions mvcOptions)
    {
        // 审计日志过滤器
        mvcOptions.Filters.Add(typeof(AuditingFilter));
    }

    public void ConfigOptions(IServiceCollection services, IConfiguration configuration)
    {
        // Admin配置项
        services.Configure<AdminOptions>(configuration);
    }
}

When the system is started, it will be designated in step, call the corresponding methods of these modules, such as when invoked service.AddModulesduring the method, the module will traverse and to inject custom services and configuration items,

public static IModuleCollection AddModules(this IServiceCollection services, IHostingEnvironment env)
{
    var modules = new ModuleCollection();
    services.AddSingleton<IModuleCollection>(modules);

    var cfgHelper = new ConfigurationHelper();
    var cfg = cfgHelper.Load("module", env.EnvironmentName, true);

    services.Configure<ModuleCommonOptions>(cfg);

    // 遍历模块
    foreach (var module in modules)
    {
        if (module == null)
            continue;

        services.AddApplicationServices(module);

        // 判断IModuleInitializer实现是否存在
        if (module.Initializer != null)
        {
            // 注入服务
            module.Initializer.ConfigureServices(services);

            //配置配置项
            module.Initializer.ConfigOptions(services, cfg.GetSection(module.Id));

            services.AddSingleton(module);
        }
    }

    return modules;
}

At this point, all the information modules which are integrated into the system ~

Original starter: before and after the end of the separation ASP.NET Core modular framework for rapid development of 4 introduces modular realization of ideas

Guess you like

Origin www.cnblogs.com/oldli/p/10945137.html