一. 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();