ASP.NET Core .NET8 CleanArchitecture 产品级完整项目模板

一、解决方案分层结构(大厂标准 4 层洋葱架构)

plaintext

YourProject.sln
├─ YourProject.Domain                # 领域层(无任何第三方基础设施依赖)
├─ YourProject.Application           # 应用层(业务用例、CQRS、DTO、抽象仓储)
├─ YourProject.Infrastructure        # 基础设施(EF、Redis、第三方SDK、仓储实现)
└─ YourProject.Api                   # 表示层 WebAPI(控制器、鉴权、中间件、Swagger)

二、每层完整代码骨架(可直接复制新建类库)

1. 领域层 YourProject.Domain

项目引用

仅引用 Microsoft.Extensions.DependencyInjection.Abstractions禁止引用 EF、Redis、Api 等

目录结构

plaintext

Domain
├─ Entities         # 数据库实体
├─ ValueObjects     # 值对象(DDD)
├─ Events           # 领域事件
├─ Interfaces       # 仓储抽象、领域服务接口
└─ Exceptions       # 自定义业务异常
1.1 领域实体示例 Entities/User.cs

csharp

运行

namespace YourProject.Domain.Entities;

public class User
{
    public long Id { get; set; }
    public string UserName { get; set; } = string.Empty;
    public string PasswordHash { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public DateTime CreateTime { get; set; }
    public bool IsDeleted { get; set; }
}
1.2 仓储抽象接口 Interfaces/IUserRepository.cs

csharp

运行

using YourProject.Domain.Entities;

namespace YourProject.Domain.Interfaces;

public interface IUserRepository
{
    Task<User?> GetByIdAsync(long id, CancellationToken ct = default);
    Task AddAsync(User user, CancellationToken ct = default);
    Task UpdateAsync(User user, CancellationToken ct = default);
    Task DeleteAsync(User user, CancellationToken ct = default);
    Task<User?> GetByNameAsync(string userName, CancellationToken ct = default);
}
1.3 业务自定义异常 Exceptions/BusinessException.cs

csharp

运行

namespace YourProject.Domain.Exceptions;

public class BusinessException : Exception
{
    public int Code { get; }
    public BusinessException(int code, string message) : base(message)
    {
        Code = code;
    }
}

2. 应用层 YourProject.Application

项目引用

  • 项目引用:YourProject.Domain
  • NuGet:MediatR、FluentValidation、AutoMapper.Extensions.Microsoft.DependencyInjection

目录结构

plaintext

Application
├─ Dtos             # 输入输出DTO
├─ CQRS
│  ├─ Commands      # 修改操作(Add/Update/Delete)
│  └─ Queries       # 查询操作
├─ Validators       # FluentValidation校验器
├─ Mappings         # AutoMapper映射配置
└─ DependencyInjection.cs  # 应用层DI注册扩展
2.1 DTO 示例 Dtos/UserDto.cs

csharp

运行

namespace YourProject.Application.Dtos;

public class UserDto
{
    public long Id { get; set; }
    public string UserName { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public DateTime CreateTime { get; set; }
}

// 创建用户入参
public class CreateUserInput
{
    public string UserName { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
}
2.2 CQRS Command 示例 CQRS/Commands/CreateUserCommand.cs

csharp

运行

using MediatR;
using YourProject.Application.Dtos;
using YourProject.Domain.Entities;
using YourProject.Domain.Interfaces;

namespace YourProject.Application.CQRS.Commands;

public record CreateUserCommand(CreateUserInput Input) : IRequest<UserDto>;

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, UserDto>
{
    private readonly IUserRepository _userRepo;
    private readonly IMapper _mapper;

    public CreateUserCommandHandler(IUserRepository userRepo, IMapper mapper)
    {
        _userRepo = userRepo;
        _mapper = mapper;
    }

    public async Task<UserDto> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        var exist = await _userRepo.GetByNameAsync(request.Input.UserName, cancellationToken);
        if (exist != null)
            throw new BusinessException(4001, "用户名已存在");

        var user = new User
        {
            UserName = request.Input.UserName,
            PasswordHash = request.Input.Password, // 生产做加密
            Phone = request.Input.Phone,
            CreateTime = DateTime.Now
        };

        await _userRepo.AddAsync(user, cancellationToken);
        return _mapper.Map<UserDto>(user);
    }
}
2.3 参数校验 Validators/CreateUserInputValidator.cs

csharp

运行

using FluentValidation;
using YourProject.Application.Dtos;

namespace YourProject.Application.Validators;

public class CreateUserInputValidator : AbstractValidator<CreateUserInput>
{
    public CreateUserInputValidator()
    {
        RuleFor(x => x.UserName)
            .NotEmpty().WithMessage("用户名不能为空")
            .Length(2, 20).WithMessage("用户名长度2-20位");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("密码不能为空")
            .MinimumLength(6).WithMessage("密码至少6位");
    }
}
2.4 AutoMapper 映射 Mappings/MapperProfile.cs

csharp

运行

using AutoMapper;
using YourProject.Application.Dtos;
using YourProject.Domain.Entities;

namespace YourProject.Application.Mappings;

public class MapperProfile : Profile
{
    public MapperProfile()
    {
        CreateMap<User, UserDto>();
    }
}
2.5 应用层 DI 注册 DependencyInjection.cs

csharp

运行

using AutoMapper;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using YourProject.Application.Mappings;

namespace YourProject.Application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        // MediatR
        services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
        // FluentValidation 自动扫描全部校验器
        services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);
        // AutoMapper
        services.AddAutoMapper(typeof(MapperProfile));

        return services;
    }
}

3. 基础设施层 YourProject.Infrastructure

项目引用

  • 项目引用:YourProject.Domain
  • NuGet:Microsoft.EntityFrameworkCore、Npgsql/Microsoft.Data.SqlClient、StackExchange.Redis

目录结构

plaintext

Infrastructure
├─ Persistence          # EF Core数据库上下文、仓储实现
├─ Cache                # Redis缓存封装
├─ DependencyInjection.cs
3.1 EF 上下文 Persistence/AppDbContext.cs

csharp

运行

using Microsoft.EntityFrameworkCore;
using YourProject.Domain.Entities;

namespace YourProject.Infrastructure.Persistence;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<User> Users => Set<User>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 批量加载实体配置
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }
}
3.2 仓储实现 Persistence/Repositories/UserRepository.cs

csharp

运行

using Microsoft.EntityFrameworkCore;
using YourProject.Domain.Entities;
using YourProject.Domain.Interfaces;

namespace YourProject.Infrastructure.Persistence.Repositories;

public class UserRepository : IUserRepository
{
    private readonly AppDbContext _db;
    public UserRepository(AppDbContext db) => _db = db;

    public async Task<User?> GetByIdAsync(long id, CancellationToken ct = default)
    {
        return await _db.Users.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
    }

    public async Task AddAsync(User user, CancellationToken ct = default)
    {
        await _db.Users.AddAsync(user, ct);
        await _db.SaveChangesAsync(ct);
    }

    public async Task UpdateAsync(User user, CancellationToken ct = default)
    {
        _db.Users.Update(user);
        await _db.SaveChangesAsync(ct);
    }

    public async Task DeleteAsync(User user, CancellationToken ct = default)
    {
        user.IsDeleted = true;
        await UpdateAsync(user, ct);
    }

    public async Task<User?> GetByNameAsync(string userName, CancellationToken ct = default)
    {
        return await _db.Users.AsNoTracking().FirstOrDefaultAsync(x => x.UserName == userName && !x.IsDeleted, ct);
    }
}
3.3 基础设施 DI 注册 DependencyInjection.cs

csharp

运行

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using YourProject.Domain.Interfaces;
using YourProject.Infrastructure.Persistence;
using YourProject.Infrastructure.Persistence.Repositories;

namespace YourProject.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration cfg)
    {
        // EF Core
        services.AddDbContext<AppDbContext>(opt =>
            opt.UseNpgsql(cfg.GetConnectionString("DefaultConnection")));

        // 仓储实现注入
        services.AddScoped<IUserRepository, UserRepository>();

        return services;
    }
}

4. 表示层 WebApi YourProject.Api(启动入口)

NuGet 包

Microsoft.AspNetCore.Mvc.NewtonsoftJson、Swashbuckle.AspNetCore、Serilog.AspNetCore、Microsoft.AspNetCore.Authentication.JwtBearer

目录结构

plaintext

Api
├─ Controllers          # API控制器
├─ Middlewares          # 全局异常、请求日志中间件
├─ Filters              # 接口鉴权过滤器
├─ Models               # 统一返回模型
├─ Extensions           # Jwt、Swagger、跨域扩展
├─ appsettings.json
└─ Program.cs
4.1 统一返回模型 Models/ApiResult.cs

csharp

运行

namespace YourProject.Api.Models;

public class ApiResult<T>
{
    public int Code { get; set; }
    public string Msg { get; set; } = string.Empty;
    public T? Data { get; set; }

    public static ApiResult<T> Success(T? data, string msg = "操作成功")
    {
        return new ApiResult<T> { Code = 200, Msg = msg, Data = data };
    }

    public static ApiResult<T> Fail(int code, string msg)
    {
        return new ApiResult<T> { Code = code, Msg = msg };
    }
}
4.2 全局异常中间件 Middlewares/GlobalExceptionMiddleware.cs

csharp

运行

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using YourProject.Api.Models;
using YourProject.Domain.Exceptions;

namespace YourProject.Api.Middlewares;

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionMiddleware> _logger;

    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext ctx, Exception ex)
    {
        ctx.Response.ContentType = "application/json";
        ApiResult<object> res;

        if (ex is BusinessException busEx)
        {
            res = ApiResult<object>.Fail(busEx.Code, busEx.Message);
            _logger.LogWarning("业务异常:{Msg}", busEx.Message);
        }
        else
        {
            res = ApiResult<object>.Fail(500, "服务器内部错误");
            // 生产环境不返回堆栈给前端
            _logger.LogError(ex, "系统未知异常");
        }

        var json = JsonSerializer.Serialize(res);
        await ctx.Response.WriteAsync(json);
    }
}
4.3 用户控制器 Controllers/UserController.cs

csharp

运行

using MediatR;
using Microsoft.AspNetCore.Mvc;
using YourProject.Api.Models;
using YourProject.Application.CQRS.Commands;
using YourProject.Application.Dtos;

namespace YourProject.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly IMediator _mediator;
    public UserController(IMediator mediator) => _mediator = mediator;

    [HttpPost("create")]
    public async Task<ActionResult<ApiResult<UserDto>>> Create(CreateUserInput input)
    {
        var cmd = new CreateUserCommand(input);
        var data = await _mediator.Send(cmd);
        return Ok(ApiResult<UserDto>.Success(data));
    }
}
4.4 核心 Program.cs(完整启动配置,产品级)

csharp

运行

using Serilog;
using YourProject.Application;
using YourProject.Infrastructure;
using YourProject.Api.Middlewares;

var builder = WebApplication.CreateBuilder(args);

// 1. Serilog日志
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .CreateLogger();
builder.Host.UseSerilog();

// 2. 分层DI注入
builder.Services.AddControllers()
    .AddNewtonsoftJson();
builder.Services.AddEndpointsApiExplorer();

// 三层依赖注入
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

// 3. Swagger、跨域、Jwt等扩展可自行封装
builder.Services.AddCors(opt =>
{
    opt.AddPolicy("AllowAll", p =>
    {
        p.AllowAnyOrigin()
         .AllowAnyMethod()
         .AllowAnyHeader();
    });
});

var app = builder.Build();

// 生产/开发区分管道
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseAuthorization();

// 全局异常中间件放最前面
app.UseMiddleware<GlobalExceptionMiddleware>();

app.MapControllers();

try
{
    Log.Information("API服务启动");
    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "服务启动失败");
}
finally
{
    Log.CloseAndFlush();
}

三、配套 appsettings.json 基础配置

json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Host=127.0.0.1;Database=YourDb;Username=postgres;Password=123456"
  },
  "Serilog": {
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": { "path": "logs/log-.txt", "rollingInterval": "Day" }
      }
    ]
  },
  "AllowedHosts": "*"
}

四、大厂强制编码使用规则

  1. 所有 IO 操作全部 async/await,禁止 .Result / .Wait()
  2. Controller 只接收参数 + 调用 MediatR,无任何业务逻辑
  3. 数据库实体不能直接返回前端,必须经过 DTO 隔离脱敏
  4. 所有入参统一 FluentValidation 校验,不写 if 判断校验
  5. 业务异常统一抛 BusinessException,全局中间件捕获格式化返回
  6. 领域层完全解耦,不依赖 EF、Redis、API 任何外部组件
  7. 仓储只做数据存取,复杂业务逻辑全部放到 Application CQRS Handler

五、快速新建项目命令(.NET CLI)

bash

运行

# 创建四层类库+API
dotnet new classlib -n YourProject.Domain
dotnet new classlib -n YourProject.Application
dotnet new classlib -n YourProject.Infrastructure
dotnet new webapi -n YourProject.Api

# 添加项目引用
dotnet add YourProject.Application reference YourProject.Domain
dotnet add YourProject.Infrastructure reference YourProject.Domain
dotnet add YourProject.Api reference YourProject.Application
dotnet add YourProject.Api reference YourProject.Infrastructure
阅读剩余
THE END