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

posted @ 2022-03-16 16:37  Light Xun  阅读(1838)  评论(2编辑  收藏  举报