Web开发:ABP框架3——模块配置实现原理解析

一、上节回顾

运用了ABP框架,使用了EFcore进行增删改查

二、程序的入口

代码解说: 

public class Program // 定义程序主类
{
    public async static Task<int> Main(string[] args) // 主方法,返回状态码
    {
        // 配置Serilog日志
        Log.Logger = new LoggerConfiguration()
#if DEBUG // 如果是调试模式
            .MinimumLevel.Debug() // 设置最小日志级别为Debug
#else // 否则
            .MinimumLevel.Information() // 设置最小日志级别为Information
#endif
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information) // 重写Microsoft命名空间的日志级别
            .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) // 重写EF Core的日志级别
            .Enrich.FromLogContext() // 从上下文中丰富日志信息
            .WriteTo.Async(c => c.File("Logs/logs.txt")) // 异步写入日志到文件
            .WriteTo.Async(c => c.Console()) // 异步写入日志到控制台
            .CreateLogger(); // 创建日志记录器

        try
        {
            Log.Information("Starting Acme.BookStore.HttpApi.Host."); // 记录应用启动信息
            var builder = WebApplication.CreateBuilder(args); // 创建Web应用程序构建器
            builder.Host.AddAppSettingsSecretsJson() // 添加应用设置的秘密配置
                .UseAutofac() // 使用Autofac作为依赖注入容器
                .UseSerilog(); // 使用Serilog作为日志提供者

            await builder.AddApplicationAsync<BookStoreHttpApiHostModule>(); // 异步添加应用程序模块
            var app = builder.Build(); // 构建应用程序
            await app.InitializeApplicationAsync(); // 初始化应用程序
            await app.RunAsync(); // 运行应用程序
            return 0; // 返回0表示成功
        }
        catch (Exception ex) // 捕获异常
        {
            Log.Fatal(ex, "Host terminated unexpectedly!"); // 记录致命错误信息
            return 1; // 返回1表示失败
        }
        finally
        {
            Log.CloseAndFlush(); // 关闭日志并刷新
        }
    }
}

 【总结】Program.cs的主要内容:

1.引入日志,若报错写在日志里面。
2.使用了 Autofac 作为依赖注入容器。
3.添加了BookStoreHttpApiHostModule模块(配置模块、服务注册、设置数据库连接、配置中间件等)。
4.启动程序

【日志位置】

.\Acme.BookStore.HttpApi.Host\Logs

【示例日志分析】

三、模块的初始化配置1

1.时间节点

Program.cs下面这句代码执行后,服务注册前

await builder.AddApplicationAsync<BookStoreHttpApiHostModule>();

2.执行的方法

PreConfigureServices

3.初始化配置的执行顺序

顺序原理:

按顺序执行下面的PreConfigureServices方法,依赖顺序取决于模块的[DependsOn] 特性:
1.BookStoreDomainSharedModule【不依赖任何模块】
2.BookStoreApplicationContractsModule【依赖1】
3.BookStoreEntityFrameworkCoreModule【依赖BookStoreDomainModule,其又依赖于1】
4.BookStoreHttpApiModule【依赖3】
5.BookStoreHttpApiHostModule【依赖3,4】

例如: BookStoreApplicationContractsModule模块

4. 手动调节项目生成顺序

        首先,解决方案-右键【项目依赖项】

        然后可以通过勾选依赖项调整编译顺序,不过我并不建议这么做,除非整个项目的依赖非常混乱,经常导致编译出现问题,才可以这么调整!从官网下载的demo生成顺序是非常清晰的。 

结论:

        项目依赖决定编译顺序, 项目引用决定类库间的调用关系(类库A右键添加项目引用-类库B,才可以通过using 命名空间使用其实体、方法) 

5.解读生成顺序

 先实体后BLL,依赖顺序非常清晰

6.解读初始化配置的代码

1.概述

以BookStoreDomainSharedModule为例,初始化配置是这样写的:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    // 调用 BookStoreGlobalFeatureConfigurator 类的 Configure 方法,用于全局功能的配置
    BookStoreGlobalFeatureConfigurator.Configure();

    // 调用 BookStoreModuleExtensionConfigurator 类的 Configure 方法,用于模块扩展的配置
    BookStoreModuleExtensionConfigurator.Configure();
}

        上面的XXXFeatureConfigurator就是单例模式的实现(唯一实例/对象,静态方法),好处是:

  1. 调用方便:可以直接通过类名访问实例的方法,无需每次都使用 new 创建对象。

  2. 全局访问:确保整个应用中始终只有一个实例,保持状态一致性,并且随时可用。

        当然,也可以删掉上图这两个蓝色的箭头文件,直接写在PreConfigureServices方法下也可以(一般是注册服务、设置默认值、初始化资源等)。

【全局功能】需要安装Volo.Abp.GlobalFeatures包

        更多内容请访问官网,由于现实使用不是特别多,因此五个PreConfigureServices方法都可以注释掉。

    主要目的是在开发阶段允许开发者启用或禁用应用程序的特定功能。例如我开发一个图书管理系统(100个功能)给广州中学和北京中学用,广州中学只需要95个功能,北京中学需要100个,都用同一套代码,配置的时候可以禁用多余的五个功能。

2.【全局】BookStoreGlobalFeatureConfigurator.Configure();

public static class BookStoreGlobalFeatureConfigurator
{
    private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

    public static void Configure()
    {
        OneTimeRunner.Run(() =>
        {
                /* You can configure (enable/disable) global features of the used modules here.
                 *
                 * YOU CAN SAFELY DELETE THIS CLASS AND REMOVE ITS USAGES IF YOU DON'T NEED TO IT!
                 *
                 * Please refer to the documentation to lear more about the Global Features System:
                 * https://docs.abp.io/en/abp/latest/Global-Features
                 */

        });
    }
}


3.【模块】BookStoreModuleExtensionConfigurator.Configure();

public static class BookStoreModuleExtensionConfigurator
{
    private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

    public static void Configure()
    {
        OneTimeRunner.Run(() =>
        {
            ConfigureExistingProperties();
            ConfigureExtraProperties();
        });
    }

    private static void ConfigureExistingProperties()
    {
        /* You can change max lengths for properties of the
         * entities defined in the modules used by your application.
         *
         * Example: Change user and role name max lengths

           IdentityUserConsts.MaxNameLength = 99;
           IdentityRoleConsts.MaxNameLength = 99;

         * Notice: It is not suggested to change property lengths
         * unless you really need it. Go with the standard values wherever possible.
         *
         * If you are using EF Core, you will need to run the add-migration command after your changes.
         */
    }

    private static void ConfigureExtraProperties()
    {
        /* You can configure extra properties for the
         * entities defined in the modules used by your application.
         *
         * This class can be used to define these extra properties
         * with a high level, easy to use API.
         *
         * Example: Add a new property to the user entity of the identity module

           ObjectExtensionManager.Instance.Modules()
              .ConfigureIdentity(identity =>
              {
                  identity.ConfigureUser(user =>
                  {
                      user.AddOrUpdateProperty<string>( //property type: string
                          "SocialSecurityNumber", //property name
                          property =>
                          {
                              //validation rules
                              property.Attributes.Add(new RequiredAttribute());
                              property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4});
                              
                              property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = true;

                              //...other configurations for this property
                          }
                      );
                  });
              });

         * See the documentation for more:
         * https://docs.abp.io/en/abp/latest/Module-Entity-Extensions
         */
    }
}

4.【官网示例解读】

官网的例子中:

IdentityUserConsts.MaxNameLength = 99; //定义了用户名称的最大字符数为 99。
IdentityRoleConsts.MaxNameLength = 99; //定义了角色名称的最大字符数为 99。

ObjectExtensionManager.Instance.Modules() // 获取模块管理器实例
    .ConfigureIdentity(identity => // 配置身份认证模块
    {
        identity.ConfigureUser(user => // 配置用户相关设置
        {
            user.AddOrUpdateProperty<string>( // 添加或更新属性,类型为字符串
                "SocialSecurityNumber", // 属性名称为社会安全号码
                property => // 对属性进行配置
                {
                    // 添加验证规则
                    property.Attributes.Add(new RequiredAttribute()); // 该属性为必填
                    property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); // 字符串长度限制,最小4个字符,最大64个字符
                    
                    // 允许用户编辑该属性的配置
                    property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = true;

                    //...其他配置可以在这里添加
                }
            );
        });
    });

五、模块的初始化配置2

1.执行顺序

和初始化配置1差不多 ,先领域共享后应用合同,最后是主机模块(Httpapi)和ORM模块(EFcore)

BookStoreDomainSharedModule
BookStoreApplicationContractsModule
BookStoreHttpApiModule --专注接口管理
BookStoreEntityFrameworkCoreModule
BookStoreHttpApiHostModule  --专注依赖注入、配置中间件、处理跨域

如果你不清楚它们的功能,最好不要删除它。 

 2.【领域共享模块】BookStoreDomainSharedModule

public override void ConfigureServices(ServiceConfigurationContext context)
{
    Configure<AbpVirtualFileSystemOptions>(options =>
    {
        options.FileSets.AddEmbedded<BookStoreDomainSharedModule>();// 模块嵌入文件集,以便访问资源文件
    });

    Configure<AbpLocalizationOptions>(options =>
    {
        options.Resources
            .Add<BookStoreResource>("en")//寻找en.json
            .AddBaseTypes(typeof(AbpValidationResource))//指定这个资源继承自AbpValidationResource,它将包含验证错误消息等
            .AddVirtualJson("/Localization/BookStore");//各种语言json存放路径.\Acme.BookStore.Domain.Shared\Localization\BookStore\en.json

        options.DefaultResourceType = typeof(BookStoreResource);//配置初始化语言
    });

    Configure<AbpExceptionLocalizationOptions>(options =>
    {
        options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); // 将"BookStore"命名空间下的所有异常映射到BookStoreResource资源中定义的信息
    });

}

BookStoreApplicationContractsModule--未配置,不做展示

3.【API模块】BookStoreHttpApiModule

// 定义一个名为 BookStoreHttpApiModule 的类,继承自 AbpModule
public class BookStoreHttpApiModule : AbpModule
{
    // 重写 ConfigureServices 方法,用于配置服务
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // 调用配置本地化的方法
        ConfigureLocalization();
    }

    // 定义一个私有方法用于配置本地化选项
    private void ConfigureLocalization()
    {
        // 配置 AbpLocalizationOptions 的选项
        Configure<AbpLocalizationOptions>(options =>
        {
            // 获取 BookStoreResource 资源,并添加基础类型
            options.Resources
                .Get<BookStoreResource>()
                .AddBaseTypes(
                    typeof(AbpUiResource) // 添加 AbpUiResource 作为基础类型
                );
        });
    }
}

资源的添加和使用:

 4.【数据库访问模块】BookStoreEntityFrameworkCoreModule

public class BookStoreEntityFrameworkCoreModule : AbpModule
{
    // 该方法在服务配置之前调用,可以用于设置一些全局的配置。
    //public override void PreConfigureServices(ServiceConfigurationContext context)
    //{
    //    // 配置 EF Core 实体扩展映射,确保实体与数据库表之间的映射关系正确。
    //    BookStoreEfCoreEntityExtensionMappings.Configure();
    //}

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // 注册 DbContext(数据库上下文),以便在应用程序中使用。
        context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
        {
            /* 
             * 设置默认的仓储模式。
             * 如果设置 includeAllEntities 为 true,将为所有实体创建默认仓储,
             * 否则只为聚合根创建默认仓储。
             */
            options.AddDefaultRepositories(includeAllEntities: true);
        });

        // 配置数据库上下文选项,指定所使用的数据库管理系统。
        Configure<AbpDbContextOptions>(options =>
        {
            /* 
             * 修改 DBMS 的主要位置。
             * 还可以查看 BookStoreMigrationsDbContextFactory 以获取 EF Core 工具支持。
             */
            options.UseSqlServer(); // 指定使用 SQL Server 作为数据库。
        });
    }
}

【重要】这个EFcore模块类配置了上下文类(Dbcontext),是使用EFcore查表必备的第一步!

5.【主机模块】BookStoreHttpApiHostModule

public class BookStoreHttpApiHostModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration(); // 获取配置对象
        var hostingEnvironment = context.Services.GetHostingEnvironment(); // 获取托管环境信息

        ConfigureConventionalControllers(); // 配置默认控制器
        ConfigureAuthentication(context, configuration); // 配置认证机制
        ConfigureLocalization(); // 配置国际化
        ConfigureCache(configuration); // 配置缓存选项
        ConfigureVirtualFileSystem(context); // 配置虚拟文件系统
        ConfigureDataProtection(context, configuration, hostingEnvironment); // 配置数据保护
        ConfigureDistributedLocking(context, configuration); // 配置分布式锁
        ConfigureCors(context, configuration); // 配置跨域资源共享(CORS)
        ConfigureSwaggerServices(context, configuration); // 配置Swagger服务
    }

    private void ConfigureCache(IConfiguration configuration)
    {
        Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "BookStore:"; }); // 设置缓存键前缀
    }

    private void ConfigureVirtualFileSystem(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment(); // 再次获取托管环境信息

        if (hostingEnvironment.IsDevelopment()) // 检查是否处于开发环境
        {
            Configure<AbpVirtualFileSystemOptions>(options =>
            {
                // 替换嵌入资源为物理文件系统的路径
                options.FileSets.ReplaceEmbeddedByPhysical<BookStoreDomainSharedModule>(
                    Path.Combine(hostingEnvironment.ContentRootPath,
                        $"..{Path.DirectorySeparatorChar}Acme.BookStore.Domain.Shared"));
                options.FileSets.ReplaceEmbeddedByPhysical<BookStoreDomainModule>(
                    Path.Combine(hostingEnvironment.ContentRootPath,
                        $"..{Path.DirectorySeparatorChar}Acme.BookStore.Domain"));
                options.FileSets.ReplaceEmbeddedByPhysical<BookStoreApplicationContractsModule>(
                    Path.Combine(hostingEnvironment.ContentRootPath,
                        $"..{Path.DirectorySeparatorChar}Acme.BookStore.Application.Contracts"));
                options.FileSets.ReplaceEmbeddedByPhysical<BookStoreApplicationModule>(
                    Path.Combine(hostingEnvironment.ContentRootPath,
                        $"..{Path.DirectorySeparatorChar}Acme.BookStore.Application"));
            });
        }
    }

    private void ConfigureConventionalControllers()
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly); // 注册控制器所在的程序集
        });
    }

    private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
    {
        // 添加JWT身份验证支持
        context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Authority = configuration["AuthServer:Authority"]; // 设置授权服务器地址
                options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); // 设置是否需要HTTPS元数据
                options.Audience = "BookStore"; // 设置目标受众
            });
    }

    private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration)
    {
        var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; // 获取XML文件名
        var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); // 获取XML文件路径

        // 添加Swagger支持
        context.Services.AddAbpSwaggerGenWithOAuth(
            configuration["AuthServer:Authority"],
            new Dictionary<string, string>
            {
                    {"BookStore", "BookStore API"}
            },
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" }); // 设置API版本
                options.DocInclusionPredicate((docName, description) => true); // 包括所有文档
                options.CustomSchemaIds(type => type.FullName); // 使用类型全名作为模式ID
                options.IncludeXmlComments(xmlPath); // 包含XML注释
            });
    }

    private void ConfigureLocalization()
    {
        // 配置应用程序支持的语言列表
        Configure<AbpLocalizationOptions>(options =>
        {
            // 添加多种语言支持
            options.Languages.Add(new LanguageInfo("ar", "ar", "العربية"));
            options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
            options.Languages.Add(new LanguageInfo("en", "en", "English"));
            options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (UK)"));
            options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish"));
            options.Languages.Add(new LanguageInfo("fr", "fr", "Français"));
            options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in"));
            options.Languages.Add(new LanguageInfo("is", "is", "Icelandic", "is"));
            options.Languages.Add(new LanguageInfo("it", "it", "Italiano", "it"));
            options.Languages.Add(new LanguageInfo("ro-RO", "ro-RO", "Română"));
            options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar"));
            options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
            options.Languages.Add(new LanguageInfo("ru", "ru", "Русский"));
            options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak"));
            options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
            options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
            options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文"));
            options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "Deutsch", "de"));
            options.Languages.Add(new LanguageInfo("es", "es", "Español", "es"));
            options.Languages.Add(new LanguageInfo("el", "el", "Ελληνικά"));
        });
    }

    private void ConfigureDataProtection(
        ServiceConfigurationContext context,
        IConfiguration configuration,
        IWebHostEnvironment hostingEnvironment)
    {
        // 添加数据保护服务
        var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("BookStore");
        
        if (!hostingEnvironment.IsDevelopment()) // 如果不是开发环境
        {
            var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); // 连接到Redis
            dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "BookStore-Protection-Keys"); // 将密钥持久化到Redis
        }
    }
    
    private void ConfigureDistributedLocking(
        ServiceConfigurationContext context,
        IConfiguration configuration)
    {
        // 添加分布式锁服务
        context.Services.AddSingleton<IDistributedLockProvider>(sp =>
        {
            var connection = ConnectionMultiplexer
                .Connect(configuration["Redis:Configuration"]); // 连接到Redis
            return new RedisDistributedSynchronizationProvider(connection.GetDatabase()); // 返回Redis分布式锁提供者
        });
    }

    private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
    {
        // 配置跨域资源共享(CORS)
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins( // 允许特定来源
                        configuration["App:CorsOrigins"]
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.RemovePostFix("/"))
                            .ToArray()
                    )
                    .WithAbpExposedHeaders() // 使用ABP暴露的头
                    .SetIsOriginAllowedToAllowWildcardSubdomains() // 允许通配符子域名
                    .AllowAnyHeader() // 允许任何头部
                    .AllowAnyMethod() // 允许任何方法
                    .AllowCredentials(); // 允许凭据
            });
        });
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder(); // 获取应用构建器
        var env = context.GetEnvironment(); // 获取托管环境

        if (env.IsDevelopment()) // 如果是开发环境
        {
            app.UseDeveloperExceptionPage(); // 使用开发者异常页面
        }

        app.UseAbpRequestLocalization(); // 使用ABP请求本地化
        app.UseCorrelationId(); // 使用相关ID中间件
        app.UseStaticFiles(); // 使用静态文件中间件
        app.UseRouting(); // 使用路由中间件
        app.UseCors(); // 使用CORS中间件
        app.UseAuthentication(); // 使用认证中间件

        if (MultiTenancyConsts.IsEnabled) // 如果多租户特性启用
        {
            app.UseMultiTenancy(); // 使用多租户中间件
        }

        app.UseAuthorization(); // 使用授权中间件

        app.UseSwagger(); // 使用Swagger中间件
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "BookStore API"); // 设置Swagger端点
            var configuration = context.GetConfiguration(); // 获取配置
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); // 设置OAuth客户端ID
            options.OAuthScopes("BookStore"); // 设置OAuth范围
        });

        app.UseAuditing(); // 使用审计中间件
        app.UseAbpSerilogEnrichers(); // 使用ABP Serilog增强器
        app.UseUnitOfWork(); // 使用工作单元中间件
        app.UseConfiguredEndpoints(); // 使用已配置的终端
    }
}

六、流程总结

  1. 创建 Web 构建器:首先,创建一个 Web 构建器(Web Builder),这是应用程序的基础,负责配置和构建整个应用。
  2. 配置相关设置:在构建器中,进行必要的配置,包括日志记录、依赖注入容器以及其他中间件的设置。这一步骤确保应用程序具备所需的功能和服务。
  3. 添加模块:将所需的模块添加到应用中。ABP 框架采用模块化设计,不同的模块提供不同的功能,能够灵活组合以满足业务需求。
  4. 初始化和运行 Web API:最后,初始化应用程序并启动 Web API。这一步骤包括完成所有配置、注册路由,并开始监听来自客户端的请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值