ASP.NET CORE 項目搭建(2022 年 3 月版)


一. ASP.NET CORE 項目搭建(2022 年 3 月版)

自讀

沉淀了多年的技術積累,在 .NET FRAMEWORK 的框架下嘗試造過自己的輪子。

  1. 摸索着閉門造過 基於 OWIN 服務后端。
  2. 摸索着閉門造過 ORM 庫。
  3. 摸索着閉門造過 代碼生成器。
  4. 摸索着閉門造過 授權服務。
  5. 摸索着閉門造過 通用權限模塊。
  6. 摸索着閉門造過 通用請求處理模塊。
  7. 摸索着閉門造過 模塊化。
  8. 摸索着閉門造過 消息隊列。
  9. 摸索着閉門造過 工具庫。

做過的事情不少,但都是基於個人的理解,搜羅參考資料,一步步去做。過程是辛苦的,效果是實現的,開發效率也是提升的。

只是,始終是一個人,比較寂寞。

一直很想把自己的理解進行整理,記錄和共享出來,希望能夠與大家交流、學習、接收指導,由於工作時間和項目進度問題,成為了一個未能達成的心願。

也是由於微軟的改動,出現了 .NET CORE, 致使曾經造過的輪子需要重新進行安排。

.NET CORE 的出現,帶來了更多未來和可能性,是要積極擁抱的。

因此,借機記錄下摸索 .NET CORE 的點滴,希望可以堅持下去。

當下的環境

  1. 時間:2022 年 3 月
  2. .NET 版本: .NET 4.6

建立空項目 - LightXun.Core.Api

  1. Dependencies(依賴項)

    • 項目中所有的服務依賴、框架,都會被安裝在該文件夾下。
    • 現有的 Microsoft.NetCore.App.NET CORE 基礎框架, 包含了對代碼、編譯、運行、部署的處理。
    • 現有的 Microsoft.AspNetCore.App 是基於基礎框架引入的應用層框架, 包含了一系列應用層服務, 例如 認證服務、授權服務、診斷服務、HTTP 請求處理服務、文件訪問、日志記錄、依賴注入等。
  2. 依賴管理(NuGet)

    • C# 用來管理插件的工具, 用於項目構建和依賴解析的工具。
  3. appsettings.json

    • 用於配置項目的運行時信息。
    • 用於日志配置、托管服務器配置、數據庫連接配置、第三方信息、賬號密碼、token 等。
  4. Properties

    • 用於配置項目的啟動信息。
    • profiles: 配置服務器、端口信息等。
  5. Program.cs

    • 程序入口,創建虛擬托管服務器。
    • 檢查程序運行環境。
    • 加載程序集,運行系統所有核心代碼。
    • 設置環境變量和日志,以及系統的反轉控制 IOC 容器。
  6. Startup.cs

    • 集中管理了系統的依賴注入、中間件、請求通道。
    • ConfigureServices 中,管理組件依賴, 其中注入各種服務組件的依賴, 將自己的服務注入到 IOC 容器中。
    • Configure 中,用來配置 http 請求通道, 創建中間件 Middleware, 設置請求通道。
  7. 宿主

    • IIS Express 寄宿於 IIS,只運行在 Windows 中。
    • . NET CORE 內建服務器,寄宿於 KESTREL 服務器,可實現跨平台。

二. MVC 與 三層架構

MVC

  1. MVC 是軟件工程的架構方式
  2. 模型(Model)、視圖(View)、控制器(Controller)
  3. 可以輕松分離業務、數據顯示、邏輯控制
  4. 視圖(View)是用戶交互界面,僅展示數據,不處理數據,接收用戶輸入
  5. 模型(Model)是 MVC 架構核心,表示業務模型或者數據模型。包含了業務邏輯,如算法實現、數據的管理、輸出對象的封裝等等
  6. 控制器(Controller)是接收用戶的輸入,調用模型和視圖完成用戶的請求。不處理數據,僅接收用戶請求,決定調用哪個模型處理,根據模型的返回,覺得使用哪個視圖來顯示數據

三層架構

  1. UI 層,表示用戶界面
  2. BLL(Business Logic Layer)業務邏輯層,處理核心業務及數據封裝
  3. DAL(Data Access Layer)數據鏈路層,數據訪問

MVC VS 三層架構

  1. 三層架構面向接口編程,三個層級之間完全解耦,完全可替換
  2. MVC 的每個部分緊密結合,核心在於重用,而非解耦
  3. 三層架構是一個自上而下的,具有明顯層級的架構
  4. MVC 架構是水平的,只有調用關系,沒有層級關系,所有數據的流動都是通過數據綁定和事件驅動處理的

MVC 的優點

  1. 耦合性低
  2. 復用性高
  3. 維護性高

MVC 的缺點

  1. 定義不明確
  2. 結構復雜
  3. 數據流動效率低

三. 增加對 Controller 的支持

program.cs

Program.cs 中, 注冊 Controller 服務組件和中間件

...
var services = builder.Services;
// 注入 MVC 組件服務
services.AddControllers(setupAction => {
  // 設置是否忽略請求頭部的 mediatype 的定義, false 為統一回復默認數據結構為 json
  setupAction.ReturnHttpNotAcceptable = true;
})
...
// 啟用 MVC 路由映射中間件
app.MapControllers();
...

創建測試 Controller

  1. 創建類文件,以 Controller 結尾、
  2. 類添加路由標簽,引入 Microsoft.AspNetCore.MVC 庫。
  3. 類繼承與 ControllerBase
  • 將類配置為 API 控制器類,有以下三種方法:
      1. 直接在類名后加入 Controller 字樣。
      1. 在類上方加入標簽 [Controller] 屬性。
      1. 將類直接繼承於 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

安裝插件

  1. Microsoft.EntityFrameworkCore - EF Core 組件
  2. Microsoft.EntityFrameworkCore.SqlServer - EF Core 對 SqlServer 支持
  3. Microsoft.EntityFrameworkCore.Tools - EF Core 工具

創建 User 實體

  1. 創建 Domain.Models 項目 - LightXun.Core.Domain.Models
  2. 創建 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 后, 仍無效的問題。

思路

  1. 建立含有配置 依賴服務 和 中間件 方法的接口或抽象類。此處由於需要實現內部方法,選擇了抽象類。
  2. 建立配置服務和中間件的上下文關系對象。承載了對應的配置、服務、環境變量。
  3. 建立模塊化上下文關系對象。包含了模塊實現類集合、自我發現模塊程序集、執行配置服務和配置中間件的方法。
  4. 建立模塊化過濾器。繼承於 IStartupFilter ,執行了模塊化關系對象的配置中間件方法。
  5. 擴展 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();


工作原因,轉回 JAVA 停更,當下源碼 gitee 項目鏈接如下。

https://gitee.com/lightxun/asp-net-core-service


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM