当前位置: 首页 > news >正文

.NET 7.0 EF Core:一、创建Web API 项目基础框架和用户表的增删改查

demo 地址: https://github.com/iotjin/Jh.Admin.NETCore
代码不定时更新,请前往github查看最新代码

.NET 7.0 EF Core:一、创建Web API项目

  • 官方教程
  • 一、项目目录结构
    • 各层职责说明
      • 1️⃣ Admin.NETCore.API(接口层)
      • 2️⃣ Admin.NETCore.Core(核心业务层)
      • 3️⃣ Admin.NETCore.Infrastructure(基础设施层)
      • 4️⃣ Admin.NETCore.Common(通用功能层)
    • 项目文件结构图
    • 设计的用户表数据结构和返回结构
  • 二、创建Web API项目
  • 三、调整项目结构
  • 四、添加NuGet 包
  • 五、通过CodeFirst建库建表
    • BaseEntity代码
    • User代码
    • UserConfig代码
    • AppDbContext代码
    • 生成数据库和表
  • 六、UserService 实现
    • Services主要代码
    • UserDTO代码
    • UserVModel代码
    • IUserService代码
    • UserService代码
  • 七、UserController 实现
    • UserController代码
  • 八、优化Program.cs
    • Program.cs原来的代码
    • ValidationFiter代码
    • ApplicationBuilderExtensions代码
    • CorsExtensions代码
    • ServiceCollectionExtensions代码
    • SwaggerExtensions代码
    • 修改后的Program.cs代码
    • 运行结果

官方教程

EF Core 官方文档
官方文档: 教程:使用 ASP.NET Core 创建最小 API
EFCore 查询数据
第一个EFCore应用
使用 NuGet 包管理器在 Visual Studio 中安装和管理包
常见的数据库提供程序

NuGet 包网站

本文记录如何使用 MySQL+ .NET 7 + Entity Framework Core (EF Core) + Code First + Fluent API 搭建一个后台管理系统框架。项目采用常见的分层结构,适合后续扩展用户、角色、菜单管理等模块。

  • Admin.NETCore.API:接口入口层(API 层),Web API 入口
  • Admin.NETCore.Core:核心业务层,Interface、Service、ViewModel、DTO等
  • Admin.NETCore.Infrastructure:基础设施层,数据库、Fluent API配置、Entity、Migrations等
  • Admin.NETCore.Common:通用功能层,工具类等

一、项目目录结构

Admin.NETCore
├── Admin.NETCore.API // 接口入口层
├── Admin.NETCore.Core // 核心业务层
├── Admin.NETCore.Infrastructure // 基础设施层
└── Admin.NETCore.Common // 通用功能层

各层职责说明

1️⃣ Admin.NETCore.API(接口层)

负责处理 HTTP 请求和响应,是整个项目的对外入口。

主要目录:

  • Controllers/:控制器,如 UserControllerRoleController
  • Identity/Filters/:请求验证过滤器,如 ValidationFilter.cs
  • ServiceExtensions/:服务注册扩展类(中间件、Swagger、CORS 等)
  • Program.cs:程序主入口
  • appsettings.json:配置文件

2️⃣ Admin.NETCore.Core(核心业务层)

包含系统的业务逻辑、接口定义、DTO/ViewModel,不依赖基础设施层。

主要目录:

  • Interfaces/:接口定义,如 IUserServiceIRoleService
  • Services/:核心业务逻辑的实现
  • ViewModels/:DTO/ViewModel 传输模型,分为 UserDTO, RoleDTO, UserVModel

3️⃣ Admin.NETCore.Infrastructure(基础设施层)

实现所有对数据库的访问逻辑,包含 EF Core 实体、配置和上下文等。

主要目录:

  • Entities/:实体类,如 User, Role, UserRole,继承 BaseEntity
  • Configs/:EF Core 实体映射配置(Fluent API)
  • AppDbContext.cs:EF Core 数据上下文
  • Migrations/:EF Core 数据迁移记录(Code First)

4️⃣ Admin.NETCore.Common(通用功能层)

放置整个项目中通用的类和工具方法。

主要目录:

  • Configs/:全局配置类,如 GlobalConfigs
  • SqlHelper.cs:通用数据库操作帮助类(如直接操作 SQL)

项目文件结构图

依赖关系
在这里插入图片描述

完整层级
在这里插入图片描述

设计的用户表数据结构和返回结构

在这里插入图片描述

二、创建Web API项目

先创建一个带swagger的Web API项目然后再对其进行改造

打开 Visual Studio → 新建项目
选择 ASP.NET Core Web API
项目命名为:Admin.NETCore.API ,解决方案命名为:Admin.NETCore
勾选:
✔ 配置 HTTPS(可选)
✔ 勾选“使用控制器(取消选中以使用最小API)”(我们保留默认控制器)
✔ 启用 OpenAPI 支持(Swagger)

创建完成点击运行,可以看到浏览器的swagger页面

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、调整项目结构

右键解决方案,按下面步骤依次创建Admin.NETCore.CommonAdmin.NETCore.CoreAdmin.NETCore.Infrastructure

右键解决方案 → 添加 → 新建项目
选择:类库 (.NET Class Library)
命名为:Admin.NETCore.Common,
选择NET 7.0 (或者创建完成后,右键项目 → 属性 → 将目标框架设置为 .NET 7.0)
创建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、添加NuGet 包

官方文档:使用 NuGet 包管理器在 Visual Studio 中安装和管理包

使用 EFCore 和MySQL 需要添加以下NuGet包:

  • Microsoft.EntityFrameworkCore.Tools
    EF Core 命令支持,用于数据库生成、迁移、生成表等操作,安装在Admin.NETCore.API

  • Pomelo.EntityFrameworkCore.MySql
    MySQL支持,安装在Admin.NETCore.Infrastructure

如果是其他数据库参考下图 常见的数据库提供程序
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

五、通过CodeFirst建库建表

第一个EFCore应用

1)、先在Admin.NETCore.Infrastructure 下面创建文件夹和文件
2)、然后通过实体类定义用户表(User)的数据结构,使用 Fluent API 在独立的配置类中集中管理表名、主键、字段约束等数据库映射信息
3)、在数据库上下文(AppDbContext)中统一注册实体集合,并自动应用所有配置类
4)、最后使用 EF Core 提供的迁移与数据库更新命令(Add-Migration 和 Update-Database)即可在数据库中生成对应的 User 表结构,实现从代码到数据库的完整同步

在这里插入图片描述

BaseEntity代码

using System.ComponentModel.DataAnnotations.Schema;namespace Admin.NETCore.Infrastructure.DB.Entities
{public abstract class BaseEntity{//public Guid Id { get; set; }  // 使用 Guid 类型,表示 UUID// Guid.NewGuid().ToString().ToLower() 得到id//public string Id { get; set; } = null!;  // 存36位字符串 char    主键要定义基类中public DateTime CreateTime { get; set; }public DateTime UpdateDate { get; set; }public string? CreateBy { get; set; }public string? UpdateBy { get; set; }public bool IsDelete { get; set; } // 是否删除//[Column(TypeName = "DATETIME(6)")] // MySQL 支持高精度时间//public DateTime CreateTime { get; set; } = DateTime.UtcNow;//[Column(TypeName = "DATETIME(6)")]//public DateTime UpdateDate { get; set; } = DateTime.UtcNow;//[Column(TypeName = "VARCHAR(10)")] // 明确指定 VARCHAR 类型//public string CreateBy { get; set; } = "system";//[Column(TypeName = "VARCHAR(10)")]//public string UpdateBy { get; set; } = "system";//[Column(TypeName = "TINYINT(1)")] // MySQL 通常用 TINYINT 表示 bool//public bool IsDelete { get; set; }}
}

User代码

namespace Admin.NETCore.Infrastructure.DB.Entities
{public class User : BaseEntity{//public Guid Id { get; set; }  // 使用 Guid 类型,表示 UUID// Guid.NewGuid().ToString().ToLower() 得到idpublic string Id { get; set; } = null!;  // 存36位字符串 charpublic string Name { get; set; } = null!;  // 姓名public string LoginName { get; set; } = null!;  // 登录用户名public string Phone { get; set; } = null!;  // 手机号public int UserNumber { get; set; }  // 员工编号public string DeptId { get; set; } = null!;  // 部门IDpublic DateTime UserExpiryDate { get; set; }  // 用户有效期止public int Status { get; set; }  // 用户状态(1:启用,0:停用)public int Level { get; set; }  // 级别(1:一级,2:二级 。。。)public double? Money { get; set; }  // 补助,可为空public int? Age { get; set; }  // 年龄,可为空public string? Notes { get; set; }  // 备注,最多100个字符}
}

UserConfig代码

using Admin.NETCore.Infrastructure.DB.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace Admin.NETCore.Infrastructure.DB.configs
{public class UserConfig : IEntityTypeConfiguration<User>{public void Configure(EntityTypeBuilder<User> builder){// 设置表名builder.ToTable("User");builder.HasKey(e => e.Id); // 设置主键// 属性配置builder.Property(e => e.Id).HasMaxLength(36);builder.Property(e => e.Name).HasMaxLength(10).IsRequired().HasComment("姓名");builder.Property(e => e.LoginName).HasMaxLength(10).HasColumnType("varchar(10)").IsRequired();builder.Property(e => e.Phone).HasMaxLength(11).IsRequired().IsFixedLength().HasAnnotation("PhoneNumber", "^[0-9]{11}$"); // 正则校验(可以自定义更复杂的格式)builder.Property(e => e.UserNumber).HasMaxLength(8).IsRequired();  // 数值类型(如 int)无法添加长度限制,此处HasMaxLength不生效builder.Property(e => e.DeptId).IsRequired(); // 不设置长度,类型为longtextbuilder.Property(e => e.UserExpiryDate).HasMaxLength(10).IsRequired(); // DateTime类型,无法添加长度限制,此处HasMaxLength不生效builder.Property(e => e.Status).IsRequired();builder.Property(e => e.Level).IsRequired();builder.Property(e => e.Money).IsRequired(false);builder.Property(e => e.Age).IsRequired(false).HasAnnotation("Age", "^[1-9][0-9]*$");builder.Property(e => e.Notes).HasMaxLength(100).IsRequired(false).HasComment("这是备注");// 公共属性配置builder.Property(e => e.CreateBy).IsRequired(false).HasMaxLength(10).HasColumnName("CreateBy");builder.Property(e => e.UpdateBy).IsRequired(false).HasMaxLength(10).HasColumnName("UpdateBy");// 创建时间(只设置一次)builder.Property(e => e.CreateTime).HasColumnName("CreateTime").HasColumnType("DATETIME(6)").ValueGeneratedOnAdd().HasDefaultValueSql("CURRENT_TIMESTAMP(6)") // GETDATE() SQL Server  / NOW() mysql  GETUTCDATE().Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);// 更新时间(每次更新)builder.Property(e => e.UpdateDate).HasColumnName("UpdateDate").HasColumnType("DATETIME(6)").ValueGeneratedOnAddOrUpdate().HasDefaultValueSql("CURRENT_TIMESTAMP(6)") // .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);//builder.Ignore(e => e.CreateTime);// 设置某字段不映射到数据表//builder.Property(e => e.IsDelete).HasColumnName("delFlag"); // 某字段对应数据表的某字段}}
}/*EF默认规则是“主键属性不允许为空,引用类型允许为空,可空的值类型long?等允许为空,值类型不允许为空。”
基于“尽量少配置”的原则:如果属性是值类型并且允许为null,就声明成long?等,否则声明成long等;
如果属性属性值是引用类型,只有不允许为空的时候设置IsRequired()。其他一般不用设置的
主键:this.HasKey(p => p.pId);
某个字段不参与映射数据库:this.Ignore(p => p.Name1);
this.Property(p => p.Name).IsFixedLength(); 是否对应固定长度
this.Property(p => p.Name).IsUnicode(false) 对应的数据库类型是varchar类型,而不是nvarchar
this.Property(p => p.Id).HasColumnName(“Id”); Id列对应数据库中名字为Id的字段
this.Property(p=>p.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity) 指定字段是自动增长类型。*/

AppDbContext代码

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Reflection;
using Admin.NETCore.Infrastructure.DB.Entities;namespace Admin.NETCore.Infrastructure.DB
{/// <summary>/// 应用数据库上下文,用于管理 EF Core 与数据库之间的交互/// </summary>public class AppDbContext : DbContext{public AppDbContext(DbContextOptions<AppDbContext> options) : base(options){}/*用户表(User 实体)对应的数据集合,AppDbContext 可以对 User 实体进行增删查改操作DbSet<T> 是 EF Core 中用于操作某个实体类型的集合放在 AppDbContext 中,EF Core 会自动识别这些属性,对应到数据库的表,进行 Code First 映射*/public DbSet<User> User { get; set; } //  DbSet<模型> 表名public DbSet<Role> Role { get; set; }public DbSet<UserRole> UserRole { get; set; }/** 数据库配置建议在 Program.cs 中完成(使用依赖注入方式)* 如果在这里同时使用 OnConfiguring,会导致配置冲突或冗余。* 所以此处注释掉 OnConfiguring 方法*///protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)//{//    // 数据库连接字符串//    var connectionString = "server=localhost;Database=TestDB;Uid=root;Pwd=root;";//    // 使用 MySQL 数据库,并指定服务器版本//    optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));//     base.OnConfiguring(optionsBuilder);//}/// <summary>/// 模型构建方法,用于配置实体与数据库的映射关系/// </summary>protected override void OnModelCreating(ModelBuilder modelBuilder){// ❎ 下面是手动配置实体属性的示例,仅供参考,实际建议写在单独的配置类中//modelBuilder.Entity<User>().Property(e => e.Name)//    .IsRequired()//    .HasComment("备注");//modelBuilder.Entity<User>(entity =>//{//    entity.ToTable("User");//    // 属性配置//    entity.HasKey(e => e.Id); // 设置主键//    entity.Property(e => e.Id).HasMaxLength(36);//    entity.Property(e => e.Name).HasMaxLength(10).IsRequired().HasComment("姓名");//    entity.Property(e => e.LoginName).HasMaxLength(10).IsRequired();//    entity.Property(e => e.Phone).HasMaxLength(11).IsRequired().IsFixedLength().HasAnnotation("PhoneNumber", "^[0-9]{11}$"); // 正则校验(可以自定义更复杂的格式)//    entity.Property(e => e.UserNumber).HasMaxLength(8).IsRequired();//    entity.Property(e => e.DeptId).IsRequired();//    entity.Property(e => e.UserExpiryDate).HasMaxLength(10).IsRequired();//    entity.Property(e => e.Status).IsRequired();//    entity.Property(e => e.Level).IsRequired();//    entity.Property(e => e.Money).IsRequired(false);//    entity.Property(e => e.Age).IsRequired(false).HasAnnotation("Age", "^[1-9][0-9]*$");//    entity.Property(e => e.Notes).HasMaxLength(100).IsRequired(false).HasComment("这是备注");//});base.OnModelCreating(modelBuilder);//方式一. 分开注册, 逐个添加实体的配置类//modelBuilder.Configurations.Add(new UserConfig());//modelBuilder.Configurations.Add(new RoleConfig());//方式二. 一次性加载所有Fluent API的配置// ✅ 推荐方式:统一加载所有 IEntityTypeConfiguration 的配置类(如 UserConfig、RoleConfig 等)modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());}}}/* Migration的一些命令(因为在Program中配置了 MigrationsAssembly对应的项目,这里放在配置的项目路径下) 工具>NuGet 包管理器 > 程序包管理器控制台Add-Migration Init update-database 指定 Migration 路径Add-Migration updateUserTable6 -OutputDir "DB/Migrations"*/

生成数据库和表

1)、在 Program.cs中注册 AppDbContext,并指定 EF Core 使用 MySQL 作为数据库

builder.Services.AddDbContext<AppDbContext>(options =>options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"),ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("DefaultConnection")),x => x.MigrationsAssembly("Admin.NETCore.Infrastructure") // 指定迁移程序集
));var app = builder.Build();

2)、在 appsettings中添加连接字符串

  "ConnectionStrings": {"DefaultConnection": "server=localhost;Database=TestDB;Uid=root;Pwd=root;"}

3)、生成Migration

// Install-Package Microsoft.EntityFrameworkCore.Tools
Add-Migration InitialCreate
Update-Database

通过Add-Migration xxx 命令生成Migration 迁移文件
作用:用于创建数据库迁移文件。根据当前的实体模型和配置类,生成一份数据库结构的变更快照(Migration 迁移文件)

在这里插入图片描述
在这里插入图片描述
4)、应用到数据库

通过Update-Database 命令将迁移应用到数据库中,创建相应的数据表结构

在这里插入图片描述
在这里插入图片描述

六、UserService 实现

1)、先在Admin.NETCore.CoreAdmin.NETCore.Common下面创建文件夹和文件
2)、在Service实现代码

在这里插入图片描述

Services主要代码

最新代码UserService.cs

UserDTO代码

using Admin.NETCore.Core.ViewModels.Base;namespace Admin.NETCore.Core.ViewModels
{public class UserDTO : BaseModel{public string Id { get; set; } = null!;public string Name { get; set; } = null!;public string LoginName { get; set; } = null!;public string Phone { get; set; } = null!;public int UserNumber { get; set; }public string DeptId { get; set; } = null!;public DateTime UserExpiryDate { get; set; }public int Status { get; set; }public int Level { get; set; }public double? Money { get; set; }public int? Age { get; set; }public string? Notes { get; set; }}
}

UserVModel代码

using Admin.NETCore.Core.ViewModels.Base;
using System.ComponentModel.DataAnnotations;namespace Admin.NETCore.Core.ViewModels
{public class UserVModel : BaseModel{public string? Id { get; set; }[Required(ErrorMessage = "Name不能为空")]public string Name { get; set; } = null!;[Required(ErrorMessage = "LoginName不能为空")]public string LoginName { get; set; } = null!;//[MaxLength(11, ErrorMessage = "Phone必须为11位")]//[MinLength(11, ErrorMessage = "Phone必须为11位")]//[StringLength(11, MinimumLength = 11, ErrorMessage = "Phone长度必须为11位[Required(ErrorMessage = "Phone不能为空")][RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "手机号格式不正确")]public string Phone { get; set; } = null!;[Required(ErrorMessage = "UserNumber不能为空")][RegularExpression(@"^\d{8}$", ErrorMessage = "UserNumber必须为8位纯数字")]public int UserNumber { get; set; }[Required(ErrorMessage = "DeptId不能为空")]public string DeptId { get; set; } = null!;public DateTime UserExpiryDate { get; set; }public int Status { get; set; }[Range(1, 10, ErrorMessage = "Level需在0-10范围内")]public int Level { get; set; }public double? Money { get; set; }[Range(1, int.MaxValue, ErrorMessage = "Age必须是正整数")]public int? Age { get; set; }public string? Notes { get; set; }}public class UserFilterModel{public string? Name { get; set; }public string? UserNumber { get; set; }public string? DeptId { get; set; }public string? StartDate { get; set; }public string? EndDate { get; set; }public int Page { get; set; }public int Limit { get; set; }}public class AssignRoleVModel{[Required(ErrorMessage = "UserId不能为空")]public string Id { get; set; } = null!;public List<string> RoleIds { get; set; } = new();}
}

IUserService代码


using Admin.NETCore.Core.ViewModels;
using Admin.NETCore.Core.ViewModels.Base;namespace Admin.NETCore.Core.Interfaces
{public interface IUserService{Task<ApiResult<UserVModel>> CreateUserAsync(UserVModel user);Task<ApiResult<UserVModel>> UpdateUserAsync(UserVModel model);Task<ApiResult<UserVModel>> GetUserByIdAsync(string id);Task<ApiResult<string>> DeleteUserByIdAsync(string id);Task<PagedResult<UserDTO>> GetUserListAsync(UserFilterModel filter);}
}

UserService代码

using Microsoft.EntityFrameworkCore;
using Admin.NETCore.Common.Configs;
using Admin.NETCore.Core.Interfaces;
using Admin.NETCore.Core.ViewModels;
using Admin.NETCore.Core.ViewModels.Base;
using Admin.NETCore.Infrastructure.DB;
using Admin.NETCore.Infrastructure.DB.Entities;namespace Admin.NETCore.Core.Services
{public class UserService : IUserService{private readonly AppDbContext _context;public UserService(AppDbContext context){_context = context;}public async Task<ApiResult<UserVModel>> CreateUserAsync(UserVModel model){//var result = new ApiResult<UserVModel>();// 检查用户名是否已存在if (await _context.User.AnyAsync(m => m.LoginName == model.LoginName)){return ApiResult<UserVModel>.FailResult("登录名已存在");}var dbModel = new User{Id = Guid.NewGuid().ToString(),Name = model.Name,LoginName = model.LoginName,Phone = model.Phone,UserNumber = model.UserNumber,DeptId = model.DeptId,UserExpiryDate = model.UserExpiryDate,Status = model.Status,Level = model.Level,Money = model.Money,Age = model.Age,Notes = model.Notes,IsDelete = false};await _context.User.AddAsync(dbModel);await _context.SaveChangesAsync();var returnModel = model;returnModel.Id = dbModel.Id;return ApiResult<UserVModel>.SuccessResult(returnModel, "用户创建成功");}public async Task<ApiResult<UserVModel>> UpdateUserAsync(UserVModel model){var user = await _context.User.FindAsync(model.Id);if (user == null){return ApiResult<UserVModel>.FailResult("用户不存在");}//    检查用户名是否与其他用户重复if (await _context.User.AnyAsync(m => m.LoginName == model.LoginName && m.Id != model.Id)){return ApiResult<UserVModel>.FailResult("登录名已存在");}user.Name = model.Name;user.LoginName = model.LoginName;user.Phone = model.Phone;user.UserNumber = model.UserNumber;user.DeptId = model.DeptId;user.UserExpiryDate = model.UserExpiryDate;user.Status = model.Status;user.Level = model.Level;user.Money = model.Money;user.Age = model.Age;user.Notes = model.Notes;user.IsDelete = false;await _context.SaveChangesAsync();var returnModel = model;returnModel.Id = user.Id;return ApiResult<UserVModel>.SuccessResult(returnModel, "用户更新成功");}public async Task<ApiResult<string>> DeleteUserByIdAsync(string id){var user = await _context.User.FindAsync(id);if (user == null){return ApiResult<string>.FailResult("用户不存在");}_context.User.Remove(user); // 物理删除//user.IsDelete = true; // 逻辑删除await _context.SaveChangesAsync();return ApiResult<string>.SuccessResult("", "用户删除成功");}public async Task<ApiResult<UserVModel>> GetUserByIdAsync(string id){var user = await _context.User.FindAsync(id);if (user == null){return ApiResult<UserVModel>.FailResult("用户不存在");}var returnModel = new UserVModel{Id = user.Id,Name = user.Name,LoginName = user.LoginName,Phone = user.Phone,UserNumber = user.UserNumber,DeptId = user.DeptId,UserExpiryDate = user.UserExpiryDate,Status = user.Status,Level = user.Level,Money = user.Money,Age = user.Age,Notes = user.Notes,IsDelete = user.IsDelete};return ApiResult<UserVModel>.SuccessResult(returnModel);}public async Task<PagedResult<UserDTO>> GetUserListAsync(UserFilterModel filter){// 参数校验filter.Page = filter.Page > 0 ? filter.Page : 1;filter.Limit = filter.Limit > 0 ? filter.Limit : GlobalConfigs.DefaultPageSize;var query = _context.User.AsNoTracking().AsQueryable();/*EF Core 默认会追踪查询出来的实体(用于之后的更新或删除)如果只是读取数据,无需修改,可以用 .AsNoTracking() 提高性能它会 减少内存开销 和 避免不必要的跟踪逻辑*///var query = _context.User.Where(m => m.IsDelete == false).AsNoTracking().AsQueryable();//var query = _context.User.Where(m => !m.IsDelete).AsNoTracking();// 姓名模糊查询if (!string.IsNullOrEmpty(filter.Name))query = query.Where(m => EF.Functions.Like(m.Name, $"%{filter.Name}%"));// 员工编号模糊查询if (!string.IsNullOrEmpty(filter.UserNumber))query = query.Where(m => m.UserNumber.ToString().Contains(filter.UserNumber));// 部门ID模糊查询if (!string.IsNullOrEmpty(filter.DeptId))query = query.Where(m => m.DeptId == filter.DeptId);// 处理日期范围if (DateTime.TryParse(filter.StartDate, out DateTime startDate))query = query.Where(m => m.CreateTime >= startDate);if (DateTime.TryParse(filter.EndDate, out DateTime endDate))query = query.Where(m => m.CreateTime <= endDate);// 获取总数int total = await query.CountAsync();// 分页查询var users = await query.OrderByDescending(m => m.UpdateDate).Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit).AsSplitQuery().Select(m => new UserDTO{Id = m.Id,Name = m.Name,LoginName = m.LoginName,Phone = m.Phone,UserNumber = m.UserNumber,DeptId = m.DeptId,UserExpiryDate = m.UserExpiryDate,Status = m.Status,Level = m.Level,Money = m.Money,Age = m.Age,Notes = m.Notes,IsDelete = m.IsDelete}).ToListAsync();return PagedResult<UserDTO>.SuccessResult(users, total);}}
}

七、UserController 实现

1)、先在Admin.NETCore.API 下面创建UserController文件
2)、在UserController实现代码
3)、在Program.cs 注册依赖注入

builder.Services.AddScoped<IUserService, UserService>();var app = builder.Build();

UserController代码

using Microsoft.AspNetCore.Mvc;
using Admin.NETCore.Core.Interfaces;
using Admin.NETCore.Core.ViewModels;
using Admin.NETCore.Core.ViewModels.Base;namespace Admin.NETCore.API.Controllers
{[ApiController][Route("api/[controller]/[action]")] // RPC风格, api/[控制器名称]/函数名称//[Route("api/[controller]")]  // RESTful 风格public class UserController : ControllerBase{private readonly IUserService _userService;public UserController(IUserService userService){_userService = userService;}[HttpPost]public async Task<ApiResult<UserVModel>> CreateUserAsync(UserVModel model){if (string.IsNullOrEmpty(model.LoginName)){return ApiResult<UserVModel>.FailResult("用户名不能为空");}return await _userService.CreateUserAsync(model);}[HttpPost]public async Task<ApiResult<UserVModel>> UpdateUserAsync(UserVModel model){if (string.IsNullOrEmpty(model.LoginName)){return ApiResult<UserVModel>.FailResult("用户名不能为空");}return await _userService.UpdateUserAsync(model);}//[HttpGet("{id}")] // 接口格式为 /api/user/GetUserById/123[HttpGet] // 接口格式为 /api/user/GetUserById?id=123public async Task<ApiResult<UserVModel>> GetUserByIdAsync(string id){if (string.IsNullOrEmpty(id)){return ApiResult<UserVModel>.FailResult("id不能为空");}return await _userService.GetUserByIdAsync(id);}[HttpPost]public async Task<ApiResult<string>> DeleteUserByIdAsync([FromBody] IDRequest request){if (string.IsNullOrEmpty(request.Id)){return ApiResult<string>.FailResult("id不能为空");}return await _userService.DeleteUserByIdAsync(request.Id);}[HttpGet]public async Task<PagedResult<UserDTO>> GetUserListAsync([FromQuery] UserFilterModel filter){return await _userService.GetUserListAsync(filter);}}
}

运行项目后,在swagger点创建用户接口 /api/User/CreateUser/api/User/GetUserList 如下

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

点创建用户接口 /api/User/CreateUser, 什么都不改,可以看到返回的数据并不是预期的结构,这是因为使用的是默认的校验,没有用自定义校验,在下一步优化

 {"code": 200,"msg": "success","data": {}}

在这里插入图片描述

八、优化Program.cs

现在Program.cs 完整代码如下,东西都写在一个文件了,以后如果加上像swagger配置、依赖注入、参数校验、中间件等,文件会越来越多,把代码都抽出来,保持主入口简洁

Program.cs原来的代码

using Admin.NETCore.Core.Interfaces;
using Admin.NETCore.Core.Services;
using Admin.NETCore.Infrastructure.DB;
using Microsoft.EntityFrameworkCore;var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();builder.Services.AddDbContext<AppDbContext>(options =>options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"),ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("DefaultConnection")),x => x.MigrationsAssembly("Admin.NETCore.Infrastructure") // 指定迁移程序集
));builder.Services.AddScoped<IUserService, UserService>();var app = builder.Build();// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{app.UseSwagger();app.UseSwaggerUI();
}app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();app.Run();

先把文件都创建好,然后在调整

在这里插入图片描述

ValidationFiter代码

// 自定义异常处理类
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Http;namespace Admin.NETCore.API.Identity.filters
{public class ValidationFilterAttribute : ActionFilterAttribute{public override void OnActionExecuting(ActionExecutingContext context){if (!context.ModelState.IsValid){// 提取所有错误信息var errorMessages = context.ModelState.Values.Where(x => x.Errors.Count > 0).SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToList();// 拼接错误信息为字符串var msgs = string.Join("; ", errorMessages);int statusCode = 201;// 构造自定义响应对象var response = new{code = statusCode,msg = msgs,success = false,data = ""};context.Result = new BadRequestObjectResult(response);// 自定义状态码//context.Result = new ObjectResult(response)//{//    StatusCode = statusCode //};}}}public class CustomValidationFilter : IAsyncActionFilter{public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next){if (!context.ModelState.IsValid){//var errorMessages = context.ModelState.Values//                    .Where(x => x.Errors.Count > 0)//                   .SelectMany(x => x.Errors)//                   .Select(x => x.ErrorMessage)//                   .ToList();//var msgs = string.Join("; ", errorMessages);//var response = new//{//    code = 201, // 可根据需求调整//    msg = msgs,//    success = false,//    data = ""//};//context.Result = new BadRequestObjectResult(response);context.Result = CreateValidationErrorResult(context.ModelState);return;}await next();}private static IActionResult CreateValidationErrorResult(ModelStateDictionary modelState){var errorMessages = modelState.Where(x => x.Value?.Errors.Count > 0).ToDictionary(x => x.Key,x => x.Value?.Errors.Select(error =>!string.IsNullOrEmpty(error.ErrorMessage)? error.ErrorMessage: error.Exception?.Message ?? "Unknown error").ToArray());var message = string.Join("; ", errorMessages.SelectMany(e => e.Value ?? Array.Empty<string>()));int statusCode = 201;var response = new{code = statusCode, // 可根据需求调整msg = message,errors = errorMessages,success = false,data = ""};return new BadRequestObjectResult(response); 自定义状态码//return new ObjectResult(response)//{//    StatusCode = statusCode//};}}
}

ApplicationBuilderExtensions代码

using Admin.NETCore.Infrastructure.DB;
using Microsoft.EntityFrameworkCore;namespace Admin.NETCore.API.ServiceExtensions
{public static class ApplicationBuilderExtensions{public static WebApplication ConfigureMiddlewarePipeline(this WebApplication app){// 异常处理应放在最前面app.UseExceptionHandler("/error");// Swaggerapp.UseCustomSwaggerUI();//app.UseCustomSwaggerUI22();if (app.Environment.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseHsts();app.UseHttpsRedirection();}app.UseRouting();// 健康检查端点app.MapHealthChecks("/health");// 全局日志中间件app.UseRequestLogging();app.UseCorsPolicy();app.UseAuthentication();app.UseAuthorization();app.MapControllers();// 可选:数据库自动迁移//app.ApplyMigrations<AppDbContext>();return app;}private static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder){return builder.Use(async (context, next) =>{var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();logger.LogInformation("Request {Method} {Path}", context.Request.Method, context.Request.Path);await next();logger.LogInformation("Response {StatusCode}", context.Response.StatusCode);});}public static void ApplyMigrations<TContext>(this WebApplication app) where TContext : DbContext{using var scope = app.Services.CreateScope();var db = scope.ServiceProvider.GetRequiredService<TContext>();db.Database.Migrate();}}
}

CorsExtensions代码

namespace Admin.NETCore.API.ServiceExtensions
{public static class CorsExtensions{public static IServiceCollection AddCorsPolicy(this IServiceCollection services){// 配置跨域services.AddCors(c => c.AddPolicy("any", p => p.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod()));return services;}public static IApplicationBuilder UseCorsPolicy(this IApplicationBuilder app){app.UseCors("any");return app;}}
}

ServiceCollectionExtensions代码

using Admin.NETCore.API.Identity.filters;
using Admin.NETCore.Core.Interfaces;
using Admin.NETCore.Core.Services;
using Admin.NETCore.Infrastructure.DB;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;namespace Admin.NETCore.API.ServiceExtensions
{public static class ServiceCollectionExtensions{private const string MigrationAssembly = "Admin.NETCore.Infrastructure";public static IServiceCollection AddBasicServices(this IServiceCollection services, IConfiguration configuration){// Add services to the container.services.AddControllers();services.AddHealthChecks();// 日志配置services.AddLogging(logging =>{logging.AddConfiguration(configuration.GetSection("Logging"));logging.AddConsole();logging.AddDebug();});services.AddCorsPolicy();services.AddSwaggerConfig();// services.AddSwaggerConfig22();return services;}public static IServiceCollection AddDataBaseServices(this IServiceCollection services, IConfiguration configuration){var connectionString = configuration.GetConnectionString("DefaultConnection")?? throw new InvalidOperationException("缺少数据库连接字符串配置");// services.AddDbContext<AppDbContext>(options =>services.AddDbContextPool<AppDbContext>(options =>options.UseMySql(connectionString,ServerVersion.AutoDetect(connectionString),x => x.MigrationsAssembly(MigrationAssembly) // 指定迁移程序集));services.AddScoped<IUserService, UserService>();return services;}public static IServiceCollection AddControllerWithValidation(this IServiceCollection services){services.AddControllers(options =>{options.Filters.Add<ValidationFilterAttribute>();options.Filters.Add<CustomValidationFilter>();});// 禁用默认的模型验证响应services.Configure<ApiBehaviorOptions>(options =>{options.SuppressModelStateInvalidFilter = true;});return services;}}
}/* 数据库配置// 添加数据库上下文
//builder.Services.AddDbContext<AppDbContext>(options =>
//    options.UseMySql(
//        builder.Configuration.GetConnectionString("DefaultConnection"),
//        ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("DefaultConnection"))
//    ));//builder.Services.AddDbContext<AppDbContext>(options =>
//    options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"),
//        new MySqlServerVersion(new Version(8, 0, 21))));//builder.Services.AddDbContext<AppDbContext>(options =>
//    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));//  指定迁移程序集//builder.Services.AddDbContext<AppDbContext>(options =>
//    options.UseMySql(
//        builder.Configuration.GetConnectionString("DefaultConnection"),
//        ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("DefaultConnection")),
//        x => x.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name)
//    ));*/

SwaggerExtensions代码

using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;// 通过[ApiExplorerSettings(GroupName = "v1")] 实现
namespace Admin.NETCore.API.ServiceExtensions
{public static class SwaggerExtensions{public static IServiceCollection AddSwaggerConfig(this IServiceCollection services){// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckleservices.AddEndpointsApiExplorer();//services.AddSwaggerGen();services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo{Title = "API (V1)",Version = "v1",//Description = "API 文档1",Description = "🚨 此版本已废弃,请使用 v2",});options.SwaggerDoc("v2", new OpenApiInfo{Title = "API (V2)",Version = "v2",Description = "API 文档2"});// 核心逻辑:根据 GroupName 过滤接口到对应文档options.DocInclusionPredicate((docName, apiDesc) =>{// 获取 Controller/Action 上的 GroupNamevar groupName = apiDesc.ActionDescriptor.EndpointMetadata.OfType<ApiExplorerSettingsAttribute>().FirstOrDefault()?.GroupName;// 未标记 GroupName 的接口默认显示在所有文档中if (string.IsNullOrEmpty(groupName)) return true;// 匹配当前 Swagger 文档名,只有标记了 GroupName 的接口才显示(如 v1/v2)return groupName == docName;});options.OperationFilter<InheritedObsoleteOperationFilter>(); // 注册自定义过滤器 解决 Schema ID 冲突问题//options.CustomSchemaIds(type => type.FullName); // 使用完整命名空间});return services;}public static WebApplication UseCustomSwaggerUI(this WebApplication app){// 获取 IConfigurationvar configuration = app.Services.GetRequiredService<IConfiguration>();// 获取配置EnableSwaggerbool enableSwagger = configuration.GetValue<bool>("EnableSwagger", false);if (app.Environment.IsDevelopment() && enableSwagger){app.UseSwagger();//app.UseSwaggerUI();app.UseSwaggerUI(c =>{c.SwaggerEndpoint("/swagger/v2/swagger.json", "versionV2"); // 这个顺序影响swagger select a definition 默认选中c.SwaggerEndpoint("/swagger/v1/swagger.json", "versionV1 (Deprecated)");// API 文档的默认展开方式c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.List); // 展开分组//c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None); // 默认折叠//c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.Full); // 完全展开所有 API 的详细描述(包括请求参数、响应模型等)//c.DefaultModelsExpandDepth(-1); // -1:完全折叠,不显示任何模型详情。0:展开模型的顶层属性(单层展开)。1或更高:展开到指定层级。});}// 动态处理根路径重定向app.MapGet("/", context =>{if (enableSwagger){// 重定向到 Swagger 页面context.Response.Redirect("/swagger");return Task.CompletedTask;}else{// 返回其他信息(例如 API 状态)context.Response.ContentType = "text/plain; charset=utf-8";return context.Response.WriteAsync($"API 运行中({(app.Environment.IsDevelopment() ? "Development" : "Production")}),Swagger 已禁用。");}});//app.Use(async (context, next) =>//{//    if (context.Request.Path == "/")//    {//        // 返回自定义响应//        context.Response.ContentType = "text/plain; charset=utf-8";//        await context.Response.WriteAsync("API 运行中,Swagger 已禁用");//        return;//    }//    await next();//});return app;}}// 若希望所有继承自 V1BaseController 的子类自动标记为废弃,可通过自定义过滤器实现。public class InheritedObsoleteOperationFilter : IOperationFilter{public void Apply(OpenApiOperation operation, OperationFilterContext context){// 检查控制器是否继承自带有 [Obsolete] 的基类var controllerType = context.MethodInfo.DeclaringType;var baseType = controllerType?.BaseType;if (baseType != null && baseType.GetCustomAttributes(typeof(ObsoleteAttribute), true).Any()){operation.Deprecated = true; // 标记操作已废弃operation.Description += " (已废弃:此接口所属控制器基类已废弃)";}}}
}

最后把Program.cs 调整一下,把Swagger配置项加到appsettings

  "EnableSwagger": true,

修改后的Program.cs代码


using Admin.NETCore.API.ServiceExtensions;var builder = WebApplication.CreateBuilder(args);// Services 配置
builder.Services.AddBasicServices(builder.Configuration);
builder.Services.AddDataBaseServices(builder.Configuration);
builder.Services.AddControllerWithValidation();//if (builder.Environment.IsDevelopment() && builder.Configuration.GetValue<bool>("EnableSwagger", false))
//{
//    builder.Services.AddSwaggerConfig();
//}var app = builder.Build();app.ConfigureMiddlewarePipeline();// Swagger
//if (builder.Environment.IsDevelopment() && builder.Configuration.GetValue<bool>("EnableSwagger", false))
//{
//    app.UseCustomSwaggerUI();
//}app.Run();

运行结果

运行后,页面如下

在这里插入图片描述

运行后,页面如下

在这里插入图片描述

至此结束

相关文章:

  • 企业商城建站百度免费推广登录入口
  • 空间购买网站网络推广员岗位职责
  • 网站上面的主导航条怎么做市场营销产品推广策划方案
  • 做夏促的网站有哪些湖南百度推广公司
  • wordpress安全监测浙江关键词优化
  • 企业网站维护建设ppt报个计算机培训班多少钱
  • C++ 第二阶段项目三:图形绘制库
  • PDF24 Creator(PDF工具箱)
  • 中文PDF解析准确率排名
  • 设计模式:揭秘Java原型模式——让复杂对象的创建不再复杂
  • 使用pyflink编写demo并将任务提交到yarn集群
  • 【启发式算法】RRT*算法详细介绍(Python)
  • 一篇文章了解XML
  • LeetCode 3298.统计重新排列后包含另一个字符串的子字符串数目2
  • aspose.word在IIS后端DLL中高并发运行,线程安全隔离
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | BackgroundSlider(背景滑块)
  • Web项目开发中Tomcat10+所需的jar包
  • 机器学习复习
  • 自动驾驶数据特征提取实战:用Python打开智能驾驶的新视角
  • C++包管理工具:conan2使用教程
  • 在vscode中,Python程序的内置对象、关键字、自定义函数名/类名、字符串进行着色,说明分别是什么颜色?
  • ant+Jmeter+jenkins接口自动化,如何实现把执行失败的接口信息单独发邮件?
  • Jenkins Pipeline 与 Python 脚本之间使用环境变量通信
  • Flutter 百题斩#8 | 说说 State 抽象类持有的成员变量
  • Flutter MobX 响应式原理与实战详解
  • cocos2 本地根据文本内容生成二维码