ASP.NET CORE 项目搭建(2022 年 3 月版)
一. ASP.NET CORE
项目搭建(2022 年 3 月版)
自读
沉淀了多年的技术积累,在 .NET FRAMEWORK
的框架下尝试造过自己的轮子。
- 摸索着闭门造过 基于
OWIN
服务后端。 - 摸索着闭门造过
ORM
库。 - 摸索着闭门造过 代码生成器。
- 摸索着闭门造过 授权服务。
- 摸索着闭门造过 通用权限模块。
- 摸索着闭门造过 通用请求处理模块。
- 摸索着闭门造过 模块化。
- 摸索着闭门造过 消息队列。
- 摸索着闭门造过 工具库。
做过的事情不少,但都是基于个人的理解,搜罗参考资料,一步步去做。过程是辛苦的,效果是实现的,开发效率也是提升的。
只是,始终是一个人,比较寂寞。
一直很想把自己的理解进行整理,记录和共享出来,希望能够与大家交流、学习、接收指导,由于工作时间和项目进度问题,成为了一个未能达成的心愿。
也是由于微软的改动,出现了 .NET CORE
, 致使曾经造过的轮子需要重新进行安排。
.NET CORE
的出现,带来了更多未来和可能性,是要积极拥抱的。
因此,借机记录下摸索 .NET CORE
的点滴,希望可以坚持下去。
当下的环境
- 时间:2022 年 3 月
.NET
版本:.NET 4.6
建立空项目 - LightXun.Core.Api
-
Dependencies(依赖项)
- 项目中所有的服务依赖、框架,都会被安装在该文件夹下。
- 现有的
Microsoft.NetCore.App
是.NET CORE
基础框架, 包含了对代码、编译、运行、部署的处理。 - 现有的
Microsoft.AspNetCore.App
是基于基础框架引入的应用层框架, 包含了一系列应用层服务, 例如 认证服务、授权服务、诊断服务、HTTP
请求处理服务、文件访问、日志记录、依赖注入等。
-
依赖管理(NuGet)
- C# 用来管理插件的工具, 用于项目构建和依赖解析的工具。
-
appsettings.json
- 用于配置项目的运行时信息。
- 用于日志配置、托管服务器配置、数据库连接配置、第三方信息、账号密码、token 等。
-
Properties
- 用于配置项目的启动信息。
- profiles: 配置服务器、端口信息等。
-
Program.cs
- 程序入口,创建虚拟托管服务器。
- 检查程序运行环境。
- 加载程序集,运行系统所有核心代码。
- 设置环境变量和日志,以及系统的反转控制 IOC 容器。
-
Startup.cs
- 集中管理了系统的依赖注入、中间件、请求通道。
- 在
ConfigureServices
中,管理组件依赖, 其中注入各种服务组件的依赖, 将自己的服务注入到 IOC 容器中。 - 在
Configure
中,用来配置http
请求通道, 创建中间件Middleware
, 设置请求通道。
-
宿主
IIS Express
寄宿于IIS
,只运行在Windows
中。. NET CORE
内建服务器,寄宿于KESTREL
服务器,可实现跨平台。
二. MVC 与 三层架构
MVC
- MVC 是软件工程的架构方式
- 模型(Model)、视图(View)、控制器(Controller)
- 可以轻松分离业务、数据显示、逻辑控制
- 视图(View)是用户交互界面,仅展示数据,不处理数据,接收用户输入
- 模型(Model)是 MVC 架构核心,表示业务模型或者数据模型。包含了业务逻辑,如算法实现、数据的管理、输出对象的封装等等
- 控制器(Controller)是接收用户的输入,调用模型和视图完成用户的请求。不处理数据,仅接收用户请求,决定调用哪个模型处理,根据模型的返回,觉得使用哪个视图来显示数据
三层架构
- UI 层,表示用户界面
- BLL(Business Logic Layer)业务逻辑层,处理核心业务及数据封装
- DAL(Data Access Layer)数据链路层,数据访问
MVC VS 三层架构
- 三层架构面向接口编程,三个层级之间完全解耦,完全可替换
- MVC 的每个部分紧密结合,核心在于重用,而非解耦
- 三层架构是一个自上而下的,具有明显层级的架构
- MVC 架构是水平的,只有调用关系,没有层级关系,所有数据的流动都是通过数据绑定和事件驱动处理的
MVC 的优点
- 耦合性低
- 复用性高
- 维护性高
MVC 的缺点
- 定义不明确
- 结构复杂
- 数据流动效率低
三. 增加对 Controller
的支持
program.cs
在 Program.cs
中, 注册 Controller
服务组件和中间件
...
var services = builder.Services;
// 注入 MVC 组件服务
services.AddControllers(setupAction => {
// 设置是否忽略请求头部的 mediatype 的定义, false 为统一回复默认数据结构为 json
setupAction.ReturnHttpNotAcceptable = true;
})
...
// 启用 MVC 路由映射中间件
app.MapControllers();
...
创建测试 Controller
- 创建类文件,以
Controller
结尾、 - 类添加路由标签,引入
Microsoft.AspNetCore.MVC
库。 - 类继承与
ControllerBase
。
- 将类配置为
API
控制器类,有以下三种方法:-
- 直接在类名后加入
Controller
字样。
- 直接在类名后加入
-
- 在类上方加入标签
[Controller]
属性。
- 在类上方加入标签
-
- 将类直接继承于
Controller
父类, 当类中使用this
时,会发现继承了许多Controller
相关的方法。
- 将类直接继承于
-
- 一般来说,创建控制器会以
Controller
为后缀,同时继承于Controller
来实现。
不可以使用 private, protected, protected internal, private protected
来定义控制器和函数。
using Microsoft.AspNetCore.Mvc;
namespace LightXun.Core.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController: ControllerBase
{
[HttpGet]
public IActionResult GetTest()
{
return Ok(new { data = "successed" });
}
}
}
四. 增加对 EF Core 的支持
创建 EF 项目 - LightXun.Core.Infrastructure.EntityFrameworkCore
引用项目 LightXun.Core.Domain.Models
安装插件
Microsoft.EntityFrameworkCore
- EF Core 组件Microsoft.EntityFrameworkCore.SqlServer
- EF Core 对 SqlServer 支持Microsoft.EntityFrameworkCore.Tools
- EF Core 工具
创建 User 实体
- 创建
Domain.Models
项目 -LightXun.Core.Domain.Models
- 创建
User
实体
using System.ComponentModel.DataAnnotations;
namespace LightXun.Core.Domain.Models.Users
{
public class User
{
[Key]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
}
}
创建上下文关系对象 - DataBaseContext
using LightXun.Core.Domain.Models.Users;
using Microsoft.EntityFrameworkCore;
namespace LightXun.Core.Infrastructure.EntityFrameworkCore.DataBase
{
/// <summary>
/// 上下文关系对象
/// 介于代码与数据之间的连接器, 在代码和数据库之间的引导数据流动
/// 解决单模块无法运行的帖子: https://blog.csdn.net/zhaobw831/article/details/105886605
/// </summary>
public class DataBaseContext: DbContext
{
public DataBaseContext(DbContextOptions<DataBaseContext> options): base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
}
}
同级中创建设计时工厂, 实现初始化独立
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace LightXun.Core.Infrastructure.EntityFrameworkCore.DataBase
{
internal class DesignTimeDataBaseContextFactory : IDesignTimeDbContextFactory<DataBaseContext>
{
public DataBaseContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<DataBaseContext>();
optionsBuilder.UseSqlServer($"server=localhost;Database=DMS_YUL;User ID=sa;Password=FLY_light528G;");
return new DataBaseContext(optionsBuilder.Options);
}
}
}
常用命令
-- 验证安装
dotnet ef
-- 生成迁移文件
dotnet ef migrations add xxx
-- 将迁移文件更新至数据库
dotnet ef database update
-- 删除数据库
dotnet ef database drop
-- 更新工具
dotnet tool update --global dotnet-ef
五. 实现模块化独立
参考文献: http://www.zyiz.net/tech/detail-186544.html
概述
实现功能的模块化、插件化,首先模块要可以注册自己的服务和中间件,即每个模块需要独立的 Startup
。
使用 ASP.NET CORE 共享框架
概述
随着 .NET CORE 3.0
发布, 许多 ASP.NET CORE
程序集不再作为包发布到 NuGet 。而是将这些程序集包含在通过 .NET CORE SDK 和运行时安装程序安装的 Microsoft.AspNetCore.App 共享框架中。(摘自官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/target-aspnetcore?view=aspnetcore-3.1&tabs=visual-studio)
引入 Microsoft.AspNetCore.App
框架
在项目文件中添加 <FrameworkReference>
元素 。
...
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
...
可解决 IWebHostEnvironment 在安装 Microsoft.AspNetCore.Hosting
后, 仍无效的问题。
思路
- 建立含有配置 依赖服务 和 中间件 方法的接口或抽象类。此处由于需要实现内部方法,选择了抽象类。
- 建立配置服务和中间件的上下文关系对象。承载了对应的配置、服务、环境变量。
- 建立模块化上下文关系对象。包含了模块实现类集合、自我发现模块程序集、执行配置服务和配置中间件的方法。
- 建立模块化过滤器。继承于 IStartupFilter ,执行了模块化关系对象的配置中间件方法。
- 扩展 WebHostBuilder ,在扩展方法中实现调用模块化关系对象的执行方法。
首先将各模块创建各自的 Module 类,并继承于模块抽象类,实现配置服务和配置中间件的方法,且在构造方法中,完成对依赖模块的添加。在项目启动时,通过 WebHostBuilder 的扩展方法执行模块化的自我发现,递归的收集每个模块注册所依赖模块的程序集,在过程中,识别出继承于抽象模块的类,将其添加至模块关系对象中,在最后进行逐个调用。
简单来说,扩展启动方法,找到所有继承于模块抽象类的类,逐个执行其中的抽象方法,在抽象方法中实现各自模块的依赖注入和服务、中间件的注册。
搭建
1. 创建模块的抽象类 - AStartupModel
- 内置了存放引用模块的类型 -
ReferenceModuleTypes
- 提供了添加引用模块类型的方法 -
AddReferenceModule<T> where T : AStartupModule
- 提供了获取引用模块所在程序集的方法 -
GetReferenceModuleAssemblies()
- 声明配置服务的抽象方法 -
ConfigureService()
- 声明配置中间件的抽象方法 -
Configure
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace LightXun.Core.Infrastructure.StartupModules
{
public abstract class AStartupModule
{
/// <summary>
/// 引用模块类型
/// </summary>
public ICollection<Type> ReferenceModuleTypes { get; } = new List<Type>();
/// <summary>
/// 添加引用模块
/// </summary>
/// <typeparam name="T"></typeparam>
public void AddReferenceModule<T>() where T : AStartupModule
{
if (!ReferenceModuleTypes.Contains(typeof(T)))
{
ReferenceModuleTypes.Add(typeof(T));
}
}
/// <summary>
/// 获取引用模块程序集
/// </summary>
/// <returns></returns>
public IEnumerable<Assembly> GetReferenceModuleAssemblies()
{
ICollection<Assembly> assemblies = new List<Assembly>();
foreach(var moduleType in ReferenceModuleTypes)
{
assemblies.Add(moduleType.Assembly);
}
return assemblies;
}
public abstract void ConfigureServices(IServiceCollection services, ConfigureServicesContext context);
public abstract void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context);
}
}
2. 创建配置服务和中间件的上下文关系对象 - ConfigureServicesContext, ConfigureMiddlewareContext
ConfigureServicesContext
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace LightXun.Core.Infrastructure.StartupModules
{
/// <summary>
/// 配置服务上下文关系对象
/// </summary>
public class ConfigureServicesContext
{
public ConfigureServicesContext(
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
}
}
ConfigureMiddlewareContext
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LightXun.Core.Infrastructure.StartupModules
{
/// <summary>
/// 配置中间件上下文对象
/// </summary>
public class ConfigureMiddlewareContext
{
public ConfigureMiddlewareContext(
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment,
IServiceProvider serviceProvider)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
ServiceProvider = serviceProvider;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public IServiceProvider ServiceProvider { get; }
}
}
3. 创建模块化上下文关系对象 - StartupModuleContext
- 内置了
AStartupModle
集合 -StartupModules
- 提供了收集继承于
AStartupModule
模块的方法 -DiscoverStartupModules()
- 提供了调用 AStartupModule 集合中每个 注册服务的方法 -
ConfigureServices()
- 提供了调用 AStartupModule 集合中每个 注册中间件的方法 -
Configure()
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace LightXun.Core.Infrastructure.StartupModules
{
public class StartupModuleContext
{
public ICollection<AStartupModule> StartupModules { get; } = new List<AStartupModule>();
public void DiscoverStartupModules() => DiscoverStartupModules(Assembly.GetEntryAssembly()!);
public void DiscoverStartupModules(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0 || assemblies.All(a => a == null))
{
return;
}
foreach(var type in assemblies.SelectMany(assembly => assembly.ExportedTypes))
{
if(typeof(AStartupModule).IsAssignableFrom(type) && type.IsClass)
{
// 过滤已存在的模块, 防止引用多次, 加载多次
if(StartupModules.Any(s => s.GetType().Name == type.Name))
{
continue;
}
//var instance = Activator.CreateInstance(type) as AStartupModule;
//if (instance == null)
//{
// throw new ArgumentException($"Specified startup module { type.Name } does not impolement { nameof(AStartupModule) } .");
//}
// C# 8.0 中的模式匹配, 替代上写法
if (Activator.CreateInstance(type) is not AStartupModule instance)
{
throw new ArgumentException($"Specified startup module { type.Name } does not impolement { nameof(AStartupModule) } .");
}
StartupModules.Add(instance);
DiscoverStartupModules(instance.GetReferenceModuleAssemblies().ToArray());
}
}
}
public void ConfigureSerivces(
IServiceCollection services,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment)
{
var serviceContext = new ConfigureServicesContext(configuration, webHostEnvironment);
foreach(var startup in StartupModules)
{
startup.ConfigureServices(services, serviceContext);
}
}
public void Configure(
IApplicationBuilder app,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment)
{
using var scope = app.ApplicationServices.CreateScope();
var middlewareContext = new ConfigureMiddlewareContext(configuration, webHostEnvironment, scope.ServiceProvider);
foreach(var startup in StartupModules)
{
startup.Configure(app, middlewareContext);
}
}
}
}
4. 创建模块化过滤器 - StartupModuleFilter
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LightXun.Core.Infrastructure.StartupModules
{
public class StartupModuleFilter : IStartupFilter
{
private readonly StartupModuleContext _context;
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _webHostEnvironment;
public StartupModuleFilter(
StartupModuleContext context,
IConfiguration configuration,
IWebHostEnvironment webHostEnvironment)
{
_context = context;
_configuration = configuration;
_webHostEnvironment = webHostEnvironment;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return (app =>
{
_context.Configure(app, _configuration, _webHostEnvironment);
next(app);
});
}
}
}
5. 扩展 WebHostBuilder - WebHostBuilderExtensions
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace LightXun.Core.Infrastructure.StartupModules.Extensions
{
/// <summary>
/// WebHostBuilder 扩展
/// </summary>
public static class WebHostBuilderExtensions
{
/// <summary>
/// 自主发现 StartupModule 并执行
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder) => UseStartupModules(builder, startupContext => startupContext.DiscoverStartupModules());
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, params Assembly[] assemblies) => UseStartupModules(builder, startupContext => startupContext.DiscoverStartupModules(assemblies));
public static IWebHostBuilder UseStartupModules(this IWebHostBuilder builder, Action<StartupModuleContext> action)
{
if(builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if(action == null)
{
throw new ArgumentNullException(nameof(action));
}
var startupContext = new StartupModuleContext();
action(startupContext);
builder.ConfigureServices((hostContext, services) =>
{
startupContext.ConfigureSerivces(services, hostContext.Configuration, hostContext.HostingEnvironment);
services.AddSingleton<IStartupFilter>(sp => ActivatorUtilities.CreateInstance<StartupModuleFilter>(sp, startupContext));
});
return builder;
}
}
}
6. 分离模块化配置 - CoreApiModule
using LightXun.Core.Application;
using LightXun.Core.Infrastructure.StartupModules;
namespace LightXun.Core.Api
{
public class CoreApiModule : AStartupModule
{
/// <summary>
/// 构造方法中注册依赖模块
/// </summary>
public CoreApiModule()
{
// 添加引用模块
AddReferenceModule<CoreApplicationModule>();
}
/// <summary>
/// 注入各种组件服务依赖
/// 程序运行时调用
/// ASP.NET CORE 提供了内置的 IOC 容器, 本方法用来将个人的服务注入到 IOC 容器中
/// services: 服务组件集合
/// </summary>
public override void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
{
/// 注入 MVC Controller 组件
services.AddControllers(setupAction =>
{
// false: 所有请求忽略头部 mediatype 的定义, 统一回复默认数据结构: json
// true: 对请求头进行验证
setupAction.ReturnHttpNotAcceptable = true;
});
Console.WriteLine($" { nameof(CoreApiModule) } - { nameof(ConfigureServices): Running .}");
}
/// <summary>
/// 配置 http 请求通道
/// 创建中间件 Middleware
/// 设置请求通道
/// app: 用来创建中间件
/// env: 托管服务器环境变量
/// </summary>
public override void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
{
var env = context.WebHostEnvironment;
// 如果是开发环境 - 可在项目属性或者 launchSetting 中进行配置
if (env.IsDevelopment())
{
// 当发生错误时, 可以看到错误信息
app.UseDeveloperExceptionPage();
}
// 路由中间件
app.UseRouting();
// Endpoints 中间件
app.UseEndpoints(endpoints =>
{
// 参数1: 目标 URL
// 参数2: LAMDA 表达式
endpoints.MapGet("/", async context =>
{
// 此处进行了一个短路处理
await context.Response.WriteAsync("Service is already started .");
});
/// 增加一个处理, 测试返回异常
endpoints.MapGet("/testException", async context =>
{
throw new Exception("test exception");
});
// 启动 MVC 路由映射中间件
endpoints.MapControllers();
});
Console.WriteLine($" { nameof(CoreApiModule) } - { nameof(Configure): Running .}");
}
}
}
7. 程序启动时调用 - Programs
using LightXun.Core.Infrastructure.StartupModules.Extensions;
var builder = WebApplication.CreateBuilder(args);
/// <summary>
/// 注册模块化启动
/// </summary>
builder.WebHost.UseStartupModules();
builder.Build().Run();