一、上节回顾
运用了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就是单例模式的实现(唯一实例/对象,静态方法),好处是:
-
调用方便:可以直接通过类名访问实例的方法,无需每次都使用
new
创建对象。 -
全局访问:确保整个应用中始终只有一个实例,保持状态一致性,并且随时可用。
当然,也可以删掉上图这两个蓝色的箭头文件,直接写在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(); // 使用已配置的终端
}
}
六、流程总结
- 创建 Web 构建器:首先,创建一个 Web 构建器(Web Builder),这是应用程序的基础,负责配置和构建整个应用。
- 配置相关设置:在构建器中,进行必要的配置,包括日志记录、依赖注入容器以及其他中间件的设置。这一步骤确保应用程序具备所需的功能和服务。
- 添加模块:将所需的模块添加到应用中。ABP 框架采用模块化设计,不同的模块提供不同的功能,能够灵活组合以满足业务需求。
- 初始化和运行 Web API:最后,初始化应用程序并启动 Web API。这一步骤包括完成所有配置、注册路由,并开始监听来自客户端的请求。