.NET 项目结构与规范
大约 9 分钟约 2712 字
.NET 项目结构与规范
简介
良好的项目结构是可维护代码的基础。.NET 生态中有多种分层架构,从简单的两层到 Clean Architecture 各有适用场景。本文总结常见的项目结构模式和团队开发规范。
特点
简单分层(小型项目)
两层/三层结构
SimpleApp/
├── SimpleApp.Web/ # Web 层
│ ├── Controllers/
│ ├── Views/
│ ├── wwwroot/
│ └── Program.cs
│
└── SimpleApp.Data/ # 数据层
├── Entities/
├── AppDbContext.cs
└── Repositories//// <summary>
/// 简单分层 — Controller 直接调用 DbContext
/// 适合个人项目、小型应用
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly AppDbContext _dbContext;
public ProductsController(AppDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var products = await _dbContext.Products.ToListAsync();
return Ok(products);
}
}标准分层(中型项目)
N 层架构
MediumApp/
├── src/
│ ├── MediumApp.Web/ # 表现层
│ │ ├── Controllers/
│ │ ├── Filters/
│ │ ├── Extensions/
│ │ └── Program.cs
│ │
│ ├── MediumApp.Service/ # 业务逻辑层
│ │ ├── Interfaces/
│ │ ├── ProductService.cs
│ │ └── OrderService.cs
│ │
│ ├── MediumApp.Repository/ # 数据访问层
│ │ ├── Interfaces/
│ │ ├── ProductRepository.cs
│ │ └── AppDbContext.cs
│ │
│ └── MediumApp.Model/ # 模型层
│ ├── Entities/
│ ├── DTOs/
│ └── Enums/
│
└── tests/
├── MediumApp.Service.Tests/
└── MediumApp.Repository.Tests/Service 模式
/// <summary>
/// 标准分层 — Service 封装业务逻辑
/// </summary>
// 接口定义
public interface IProductService
{
Task<List<ProductDto>> GetAllAsync(int page, int pageSize);
Task<ProductDto?> GetByIdAsync(int id);
Task<ProductDto> CreateAsync(CreateProductRequest request);
Task<bool> UpdateAsync(int id, UpdateProductRequest request);
Task<bool> DeleteAsync(int id);
}
// 服务实现
public class ProductService : IProductService
{
private readonly IProductRepository _repository;
private readonly IMapper _mapper;
private readonly ILogger<ProductService> _logger;
public ProductService(
IProductRepository repository,
IMapper mapper,
ILogger<ProductService> logger)
{
_repository = repository;
_mapper = mapper;
_logger = logger;
}
public async Task<List<ProductDto>> GetAllAsync(int page, int pageSize)
{
var (products, total) = await _repository.GetPagedAsync(page, pageSize);
return _mapper.Map<List<ProductDto>>(products);
}
public async Task<ProductDto> CreateAsync(CreateProductRequest request)
{
var product = _mapper.Map<Product>(request);
await _repository.AddAsync(product);
return _mapper.Map<ProductDto>(product);
}
}
// Controller 调用 Service
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
{
var products = await _productService.GetAllAsync(page, pageSize);
return Ok(products);
}
}模块化结构(大型项目)
按业务模块组织
EnterpriseApp/
├── src/
│ ├── Shared/ # 共享内核
│ │ ├── Events/
│ │ ├── Exceptions/
│ │ ├── Extensions/
│ │ └── Common/
│ │
│ ├── Modules/
│ │ ├── Order/ # 订单模块
│ │ │ ├── Domain/
│ │ │ │ ├── Entities/
│ │ │ │ └── Interfaces/
│ │ │ ├── Application/
│ │ │ │ ├── Commands/
│ │ │ │ ├── Queries/
│ │ │ │ └── DTOs/
│ │ │ ├── Infrastructure/
│ │ │ │ └── Repositories/
│ │ │ └── Api/
│ │ │ └── Controllers/
│ │ │
│ │ ├── Product/ # 产品模块
│ │ │ ├── Domain/
│ │ │ ├── Application/
│ │ │ ├── Infrastructure/
│ │ │ └── Api/
│ │ │
│ │ └── User/ # 用户模块
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── Api/
│ │
│ └── Gateway/ # API 网关
│ └── Program.cs
│
└── tests/
└── Modules/
├── Order.Tests/
└── Product.Tests/命名规范
项目命名
{Company}.{Project}.{Layer}
示例:
SunnyFan.Shop.Web # Web API
SunnyFan.Shop.Application # 应用层
SunnyFan.Shop.Domain # 领域层
SunnyFan.Shop.Infrastructure # 基础设施层
SunnyFan.Shop.Tests # 测试文件命名
实体: Product.cs, Order.cs
接口: IProductService.cs, IOrderRepository.cs
服务: ProductService.cs, OrderService.cs
仓储: ProductRepository.cs
DTO: ProductDto.cs, CreateProductRequest.cs
控制器: ProductsController.cs
枚举: OrderStatus.cs
异常: DomainException.cs
扩展: ServiceCollectionExtensions.cs
配置: JwtOptions.cs, DatabaseOptions.cs代码规范
/// <summary>
/// 代码规范示例
/// </summary>
// 1. 使用 PascalCase 命名类、方法、属性
public class OrderService
{
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request) { }
public int MaxRetryCount { get; set; }
}
// 2. 使用 camelCase 命名局部变量和参数
public void Process(string orderNo, int quantity)
{
var totalPrice = quantity * 100;
}
// 3. 接口以 I 开头
public interface IOrderService { }
// 4. 私有字段以 _ 开头
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository orderRepository, ILogger<OrderService> logger)
{
_orderRepository = orderRepository;
_logger = logger;
}
}
// 5. 常量使用 PascalCase
public const int MaxPageSize = 100;
public static readonly string DefaultCurrency = "CNY";
// 6. 异步方法以 Async 结尾
public async Task<OrderDto> GetByIdAsync(int id) { }
// 7. 布尔属性以 Is/Has/Can 开头
public bool IsActive { get; set; }
public bool HasPermission { get; set; }
public bool CanCancel { get; set; }配置管理
配置文件结构
appsettings.json # 基础配置
appsettings.Development.json # 开发环境
appsettings.Staging.json # 预发布环境
appsettings.Production.json # 生产环境Options 模式
/// <summary>
/// 强类型配置 — Options 模式
/// </summary>
public class JwtOptions
{
public string SecretKey { get; set; } = "";
public string Issuer { get; set; } = "";
public string Audience { get; set; } = "";
public int ExpireMinutes { get; set; } = 60;
}
public class DatabaseOptions
{
public string ConnectionString { get; set; } = "";
public int MaxRetryCount { get; set; } = 3;
public int CommandTimeout { get; set; } = 30;
}
// 注册
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
builder.Services.Configure<DatabaseOptions>(builder.Configuration.GetSection("Database"));
// 使用
public class AuthService
{
private readonly JwtOptions _jwtOptions;
public AuthService(IOptions<JwtOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
}
}全局异常处理
统一响应格式
/// <summary>
/// 统一 API 响应格式
/// </summary>
public class ApiResponse<T>
{
public int Code { get; set; }
public string Message { get; set; } = "";
public T? Data { get; set; }
public DateTime Timestamp { get; set; } = DateTime.Now;
public static ApiResponse<T> Success(T data, string message = "操作成功")
=> new() { Code = 0, Message = message, Data = data };
public static ApiResponse<T> Fail(string message, int code = -1)
=> new() { Code = code, Message = message };
}
// 全局异常中间件
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (DomainException ex)
{
_logger.LogWarning(ex, "业务异常");
await WriteResponse(context, 400, ex.Message);
}
catch (ValidationException ex)
{
await WriteResponse(context, 400, ex.Message);
}
catch (NotFoundException ex)
{
await WriteResponse(context, 404, ex.Message);
}
catch (UnauthorizedException ex)
{
await WriteResponse(context, 401, ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "未处理异常");
await WriteResponse(context, 500, "服务器内部错误");
}
}
private static async Task WriteResponse(HttpContext context, int statusCode, string message)
{
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(ApiResponse<object>.Fail(message, statusCode));
}
}项目规模选择
| 规模 | 结构 | 层数 | 适用 |
|---|---|---|---|
| 个人/小型 | 简单两层 | 2 | 个人项目、Demo |
| 中型 | N 层 + Service | 3-4 | 企业内部系统 |
| 大型 | 模块化/Clean | 4-5 | 企业核心系统 |
| 微服务 | 每服务独立 | 3-4 | 大型分布式系统 |
优点
缺点
总结
项目结构没有银弹,根据项目规模选择合适的分层。小项目简单两层即可,中项目用 Service 分层,大项目用模块化或 Clean Architecture。核心原则:简单至上,按需演进,团队统一。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《.NET 项目结构与规范》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《.NET 项目结构与规范》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《.NET 项目结构与规范》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《.NET 项目结构与规范》最大的收益和代价分别是什么?
Clean Architecture(DDD 风格)
Clean Architecture 强调依赖方向只能从外向内:基础设施层依赖应用层,应用层依赖领域层,领域层不依赖任何层。
CleanArch/
├── src/
│ ├── CleanArch.Domain/ # 领域层(无外部依赖)
│ │ ├── Entities/
│ │ │ ├── Order.cs # 聚合根
│ │ │ ├── OrderItem.cs # 实体
│ │ │ └── Money.cs # 值对象
│ │ ├── Events/
│ │ │ └── OrderCreatedEvent.cs
│ │ ├── Interfaces/
│ │ │ ├── IOrderRepository.cs
│ │ │ └── IDomainEventDispatcher.cs
│ │ ├── Enums/
│ │ │ └── OrderStatus.cs
│ │ └── Exceptions/
│ │ └── DomainException.cs
│ │
│ ├── CleanArch.Application/ # 应用层
│ │ ├── Commands/
│ │ │ ├── CreateOrderCommand.cs
│ │ │ └── CreateOrderHandler.cs
│ │ ├── Queries/
│ │ │ ├── GetOrderByIdQuery.cs
│ │ │ └── GetOrderByIdHandler.cs
│ │ ├── DTOs/
│ │ │ └── OrderDto.cs
│ │ ├── Interfaces/
│ │ │ └── IDateTimeProvider.cs
│ │ └── Behaviors/
│ │ ├── ValidationBehavior.cs
│ │ └── LoggingBehavior.cs
│ │
│ ├── CleanArch.Infrastructure/ # 基础设施层
│ │ ├── Persistence/
│ │ │ ├── AppDbContext.cs
│ │ │ └── Repositories/
│ │ ├── Services/
│ │ └── Configuration/
│ │
│ └── CleanArch.WebApi/ # 表现层
│ ├── Controllers/
│ ├── Filters/
│ ├── Extensions/
│ └── Program.cs
│
└── tests/
├── CleanArch.UnitTests/
├── CleanArch.IntegrationTests/
└── CleanArch.ArchitectureTests/ # 架构测试/// <summary>
/// Clean Architecture 领域层示例 — 纯 C#,无外部依赖
/// </summary>
// 领域实体(基类)
public abstract class Entity
{
public Guid Id { get; protected set; }
private readonly List<object> _domainEvents = new();
public IReadOnlyList<object> DomainEvents => _domainEvents.AsReadOnly();
public void AddDomainEvent(object eventItem) => _domainEvents.Add(eventItem);
public void ClearDomainEvents() => _domainEvents.Clear();
}
// 聚合根
public class Order : Entity
{
public string OrderNo { get; private set; }
public List<OrderItem> Items { get; private set; } = new();
public OrderStatus Status { get; private set; }
public DateTime CreatedAt { get; private set; }
// 工厂方法 — 确保创建时就是合法的
public static Order Create(List<OrderItem> items)
{
if (items == null || items.Count == 0)
throw new DomainException("订单必须包含至少一个商品");
var order = new Order
{
Id = Guid.NewGuid(),
OrderNo = $"ORD-{DateTime.Now:yyyyMMddHHmmss}",
Items = items,
Status = OrderStatus.Created,
CreatedAt = DateTime.UtcNow
};
order.AddDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
// 行为方法 — 业务规则封装在实体内
public void Cancel()
{
if (Status == OrderStatus.Shipped)
throw new DomainException("已发货订单无法取消");
Status = OrderStatus.Cancelled;
}
public void Pay()
{
if (Status != OrderStatus.Created)
throw new DomainException("只有创建状态的订单可以支付");
Status = OrderStatus.Paid;
}
}
// 值对象
public record Money(decimal Amount, string Currency = "CNY")
{
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency)
throw new DomainException("不能对不同币种进行加法运算");
return new Money(a.Amount + b.Amount, a.Currency);
}
public static Money Zero(string currency = "CNY") => new(0, currency);
}架构测试(NetArchTest)
架构测试确保项目依赖方向正确,防止层级违规。
// dotnet add package NetArchTest.Rules
using NetArchTest.Rules;
using FluentAssertions;
public class ArchitectureTests
{
private const string DomainNamespace = "CleanArch.Domain";
private const string ApplicationNamespace = "CleanArch.Application";
private const string InfrastructureNamespace = "CleanArch.Infrastructure";
private const string WebApiNamespace = "CleanArch.WebApi";
[Fact]
public void Domain_Should_Not_Reference_Any_Other_Project()
{
var result = Types.InAssembly(typeof(Order).Assembly)
.ShouldNot()
.HaveDependencyOnAny(ApplicationNamespace, InfrastructureNamespace, WebApiNamespace)
.GetResult();
result.IsSuccessful.Should().BeTrue();
}
[Fact]
public void Application_Should_Not_Reference_Infrastructure()
{
var result = Types.InAssembly(typeof(CreateOrderHandler).Assembly)
.ShouldNot()
.HaveDependencyOn(InfrastructureNamespace)
.GetResult();
result.IsSuccessful.Should().BeTrue();
}
[Fact]
public void Controllers_Should_Have_Dependency_On_MediatR()
{
var result = Types.InAssembly(typeof(Program).Assembly)
.That().ResideInNamespace($"{WebApiNamespace}.Controllers")
.Should()
.HaveDependencyOn("MediatR")
.GetResult();
result.IsSuccessful.Should().BeTrue();
}
}DI 注册组织方式
/// <summary>
/// 按模块组织 DI 注册,避免 Program.cs 膨胀
/// </summary>
// 每个层提供自己的注册扩展方法
// Domain 层无 DI 注册(无依赖)
// Application/CleanArch.Application/ServiceCollectionExtensions.cs
public static class ApplicationServiceExtensions
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(ApplicationServiceExtensions).Assembly);
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
});
services.AddValidatorsFromAssembly(typeof(ApplicationServiceExtensions).Assembly);
return services;
}
}
// Infrastructure/CleanArch.Infrastructure/ServiceCollectionExtensions.cs
public static class InfrastructureServiceExtensions
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("Default")));
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IDomainEventDispatcher, DomainEventDispatcher>();
return services;
}
}
// Program.cs — 简洁清晰
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddWebApi(builder.Configuration);