ASP.NET Core项目如何组织可最小化仓储、服务等模块的代码重复?
消除ASP.NET Core代码重复与大型项目组织方案
一、消除重复代码的具体方案
1. 仓储层:泛型仓储封装通用CRUD
定义通用仓储接口和基类,把重复的CRUD逻辑抽离出来,具体实体仓储仅需实现专属业务逻辑:
// 通用仓储接口 public interface IRepository<T> where T : class { Task<T?> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(int id); Task SaveChangesAsync(); } // 通用仓储实现基类 public class BaseRepository<T> : IRepository<T> where T : class { protected readonly DbContext _context; protected readonly DbSet<T> _dbSet; public BaseRepository(DbContext context) { _context = context; _dbSet = context.Set<T>(); } public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id); public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync(); public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity); public void UpdateAsync(T entity) => _dbSet.Update(entity); public async Task DeleteAsync(int id) { var entity = await GetByIdAsync(id); if (entity != null) _dbSet.Remove(entity); } public async Task SaveChangesAsync() => await _context.SaveChangesAsync(); } // 具体实体仓储,仅实现专属逻辑 public class UserRepository : BaseRepository<User>, IUserRepository { public UserRepository(AppDbContext context) : base(context) { } // 用户专属查询:根据邮箱获取用户 public async Task<User?> GetByEmailAsync(string email) { return await _dbSet.FirstOrDefaultAsync(u => u.Email == email); } }
2. 服务层:抽象基类封装通用业务逻辑
用抽象基类封装通用的实体服务逻辑,具体服务继承后专注于业务特有逻辑:
public abstract class BaseService<T> where T : class { protected readonly IRepository<T> _repository; public BaseService(IRepository<T> repository) { _repository = repository; } public async Task<T?> GetByIdAsync(int id) => await _repository.GetByIdAsync(id); public async Task<IEnumerable<T>> GetAllAsync() => await _repository.GetAllAsync(); public async Task AddAsync(T entity) { // 通用新增前校验逻辑 ValidateEntity(entity); await _repository.AddAsync(entity); await _repository.SaveChangesAsync(); } // 留给子类重写的专属校验方法 protected virtual void ValidateEntity(T entity) { } } public class UserService : BaseService<User>, IUserService { public UserService(IUserRepository repository) : base(repository) { } // 重写校验逻辑,添加用户专属规则 protected override void ValidateEntity(User entity) { if (string.IsNullOrWhiteSpace(entity.Email)) throw new ArgumentException("邮箱不能为空"); } // 用户专属业务:重置密码 public async Task ResetPasswordAsync(int userId, string newPassword) { var user = await GetByIdAsync(userId); if (user == null) throw new KeyNotFoundException("用户不存在"); user.PasswordHash = HashPassword(newPassword); _repository.UpdateAsync(user); await _repository.SaveChangesAsync(); } }
3. 用例层:通用用例模板+复杂用例单独实现
针对通用CRUD场景定义泛型用例类,复杂业务用例则单独编写,避免重复造轮子:
// 通用用例:根据ID获取实体 public class GetEntityByIdUseCase<T> where T : class { private readonly IRepository<T> _repository; public GetEntityByIdUseCase(IRepository<T> repository) { _repository = repository; } public async Task<T?> Execute(int id) => await _repository.GetByIdAsync(id); } // 复杂业务用例:创建订单(关联用户与书籍) public class CreateOrderUseCase { private readonly IOrderRepository _orderRepo; private readonly IUserRepository _userRepo; private readonly IBookRepository _bookRepo; public CreateOrderUseCase(IOrderRepository orderRepo, IUserRepository userRepo, IBookRepository bookRepo) { _orderRepo = orderRepo; _userRepo = userRepo; _bookRepo = bookRepo; } public async Task<Order> Execute(int userId, int bookId, decimal amount) { var user = await _userRepo.GetByIdAsync(userId); var book = await _bookRepo.GetByIdAsync(bookId); if (user == null || book == null) throw new ArgumentException("用户或书籍不存在"); var order = new Order { UserId = userId, BookId = bookId, Amount = amount, OrderDate = DateTime.Now }; await _orderRepo.AddAsync(order); await _orderRepo.SaveChangesAsync(); return order; } }
4. 其他复用技巧
- 对象映射:用AutoMapper定义通用映射规则,减少实体与DTO的重复转换代码
- 扩展方法:将分页、排序等通用查询逻辑封装为
IQueryable<T>的扩展方法 - 验证逻辑:用FluentValidation抽象通用验证规则,避免重复编写校验代码
二、大型ASP.NET Core项目组织方式
1. 分层架构(清晰分离关注点)
按职责划分项目层,遵循依赖倒置原则:
- 领域层:核心实体、领域服务、仓储接口,是业务核心,不依赖外部层
- 基础设施层:仓储实现、EF Core配置、第三方服务集成,依赖领域层
- 应用层:用例、应用服务、DTO,协调领域层与基础设施层,依赖领域层接口
- 表现层:API控制器、前端资源,依赖应用层
- 公共层:通用工具类、扩展方法、常量,供各层调用
2. 按业务模块拆分结构
在分层基础上,按业务模块拆分代码,便于团队并行开发:
YourProject/ ├── Domain/ │ ├── User/ │ │ ├── User.cs │ │ ├── IUserRepository.cs │ │ └── UserDomainService.cs │ ├── Book/ │ │ ├── Book.cs │ │ └── IBookRepository.cs │ └── Order/ │ ├── Order.cs │ └── IOrderRepository.cs ├── Infrastructure/ │ ├── Data/ │ │ ├── AppDbContext.cs │ │ └── Repositories/ │ │ ├── UserRepository.cs │ │ ├── BookRepository.cs │ │ └── OrderRepository.cs │ └── ExternalServices/ │ └── EmailService.cs ├── Application/ │ ├── User/ │ │ ├── DTOs/ │ │ ├── UseCases/ │ │ └── UserService.cs │ ├── Book/ │ │ ├── DTOs/ │ │ ├── UseCases/ │ │ └── BookService.cs │ └── Order/ │ ├── DTOs/ │ ├── UseCases/ │ └── OrderService.cs ├── WebApi/ │ ├── Controllers/ │ │ ├── UserController.cs │ │ ├── BookController.cs │ │ └── OrderController.cs │ └── Program.cs └── Shared/ ├── Extensions/ ├── Validators/ └── DTOs/
3. 模块化依赖注入
在Program.cs中按模块注册服务,保持配置清晰:
var builder = WebApplication.CreateBuilder(args); // 注册数据访问服务 builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IBookRepository, BookRepository>(); // 注册应用层服务 builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<CreateOrderUseCase>(); // 注册工具类服务 builder.Services.AddAutoMapper(typeof(Program)); builder.Services.AddFluentValidationAutoValidation();
4. 统一处理横切关注点
- 日志:用Serilog/NLog实现统一日志,避免各层重复编写日志代码
- 异常处理:自定义全局异常中间件,统一API异常返回格式
- 缓存:封装缓存服务,复用缓存逻辑,减少重复操作
内容的提问来源于stack exchange,提问作者Ali-Muhammad Zakaryaev
相关产品推荐
相关产品推荐

