手把手搭建C#企業(yè)級項目:從分層架構(gòu)到完整API實現(xiàn)
很多C#開發(fā)者都有這樣的困惑跟著教程學(xué)會了語法也做過一些小練習(xí)但一到實際工作中面對一個需要從零開始構(gòu)建的“標(biāo)準(zhǔn)企業(yè)級項目”時卻不知從何下手。Controller、Service、Repository這些層到底該怎么劃分依賴注入怎么配才合理數(shù)據(jù)庫連接和事務(wù)又該如何管理網(wǎng)上能找到的要么是零散的“學(xué)生管理系統(tǒng)”Demo要么是過于龐大、依賴特定商業(yè)框架的解決方案中間缺少一個清晰、完整、能直接復(fù)用到真實工作場景的橋梁。這篇文章要解決的正是這個“最后一公里”的問題。我們不只講語法而是手把手帶你搭建一個具備標(biāo)準(zhǔn)企業(yè)級架構(gòu)雛形的C#項目。你將清晰地看到一個可維護(hù)、可擴(kuò)展、職責(zé)分明的后端項目是如何從文件夾結(jié)構(gòu)開始一步步構(gòu)建起來的。我們會從最簡單的控制臺程序開始逐步引入ASP.NET Core Web API、Entity Framework Core、分層架構(gòu)、依賴注入、倉儲模式、單元測試等核心概念并最終落地一個包含用戶管理和產(chǎn)品目錄的微型業(yè)務(wù)系統(tǒng)。讀完本文你將獲得一個可以直接作為模板的完整項目結(jié)構(gòu)理解每一層存在的意義并掌握如何將新的業(yè)務(wù)需求填充到這個框架中。無論你是剛學(xué)完C#基礎(chǔ)想進(jìn)階的初學(xué)者還是有一定經(jīng)驗但想系統(tǒng)化工程實踐的開發(fā)者這篇文章都將為你提供一條明確的實踐路徑。1. 為什么你需要一個“標(biāo)準(zhǔn)”的企業(yè)級項目結(jié)構(gòu)在開始敲代碼之前我們必須先達(dá)成一個共識為什么不能把所有代碼都寫在Program.cs或者Controller里企業(yè)級項目結(jié)構(gòu)的價值遠(yuǎn)不止于“看起來規(guī)范”。核心價值是應(yīng)對變化與協(xié)作。想象一下當(dāng)業(yè)務(wù)邏輯需要修改時如果它和數(shù)據(jù)庫訪問代碼、API接口代碼糾纏在一起你很可能改一處而崩三處。當(dāng)需要更換數(shù)據(jù)庫比如從SQL Server遷移到PostgreSQL時如果SQL語句散落在上百個業(yè)務(wù)方法里這幾乎是一項不可能完成的任務(wù)。當(dāng)新同事加入面對一個沒有清晰結(jié)構(gòu)的“意大利面條式”代碼庫他的學(xué)習(xí)成本和犯錯概率會急劇上升。一個標(biāo)準(zhǔn)的分層架構(gòu)如經(jīng)典的三層或領(lǐng)域驅(qū)動設(shè)計中的分層通過分離關(guān)注點(diǎn)來解決這些問題。每一層有明確的職責(zé)表現(xiàn)層 (Presentation Layer):只負(fù)責(zé)接收HTTP請求、驗證基礎(chǔ)格式、返回響應(yīng)。它不應(yīng)該知道數(shù)據(jù)從哪里來。業(yè)務(wù)邏輯層 (Business Logic Layer / Service Layer):包含核心的業(yè)務(wù)規(guī)則和流程。它是系統(tǒng)的“大腦”協(xié)調(diào)數(shù)據(jù)流轉(zhuǎn)和業(yè)務(wù)決策。數(shù)據(jù)訪問層 (Data Access Layer / Repository Layer):負(fù)責(zé)與數(shù)據(jù)庫、文件系統(tǒng)、外部API等數(shù)據(jù)源打交道。它封裝了所有數(shù)據(jù)持久化的細(xì)節(jié)。這樣當(dāng)UI從Web API變成桌面應(yīng)用時你只需替換表現(xiàn)層當(dāng)數(shù)據(jù)庫變更時你只需調(diào)整數(shù)據(jù)訪問層。業(yè)務(wù)邏輯作為最核心的資產(chǎn)保持穩(wěn)定。接下來我們就從零開始構(gòu)建這樣一個結(jié)構(gòu)清晰的項目。2. 項目結(jié)構(gòu)與技術(shù)棧選型我們將創(chuàng)建一個名為EnterpriseDemo的解決方案。技術(shù)棧選擇當(dāng)前最主流、最穩(wěn)定的組合.NET 8 最新的LTS長期支持版本性能和支持都有保障。ASP.NET Core Web API 構(gòu)建RESTful API的事實標(biāo)準(zhǔn)。Entity Framework Core 8 ORM框架用于對象關(guān)系映射支持Code First開發(fā)模式。SQL Server LocalDB / SQLite 為了演示方便我們使用輕量級的LocalDBWindows或SQLite跨平臺生產(chǎn)環(huán)境可無縫切換至SQL Server、PostgreSQL等。xUnitMoq 用于編寫單元測試和模擬依賴。Swagger/OpenAPI 自動生成API文檔。最終的解決方案結(jié)構(gòu)如下使用Visual Studio 2022或Rider或通過CLI創(chuàng)建EnterpriseDemo.sln ├── src/ │ ├── EnterpriseDemo.API/ (表現(xiàn)層 - ASP.NET Core Web API 項目) │ ├── EnterpriseDemo.Core/ (核心層 - 實體、枚舉、接口、DTO等) │ ├── EnterpriseDemo.Application/ (應(yīng)用層 - 業(yè)務(wù)邏輯、服務(wù)、映射) │ └── EnterpriseDemo.Infrastructure/ (基礎(chǔ)設(shè)施層 - 數(shù)據(jù)訪問、外部服務(wù)集成) └── tests/ └── EnterpriseDemo.Tests/ (單元測試項目)這種結(jié)構(gòu)清晰地區(qū)分了職責(zé)并且通過項目引用而非DLL來管理依賴比如API項目引用Application和InfrastructureApplication引用CoreInfrastructure引用Core和Application對于接口。3. 環(huán)境準(zhǔn)備與項目創(chuàng)建3.1 開發(fā)環(huán)境準(zhǔn)備安裝 .NET 8 SDK: 前往 微軟官網(wǎng) 下載并安裝。安裝 IDE: 推薦使用Visual Studio 2022(社區(qū)版免費(fèi)) 并確保安裝了“ASP.NET和Web開發(fā)”工作負(fù)載?;蛘呤褂肑etBrains Rider、Visual Studio Code。數(shù)據(jù)庫: 確保已安裝SQL Server Express LocalDB(通常隨VS安裝) 或SQLite。3.2 使用 CLI 創(chuàng)建解決方案和項目打開終端PowerShell, CMD, 或 Bash執(zhí)行以下命令# 創(chuàng)建解決方案目錄并進(jìn)入 mkdir EnterpriseDemo cd EnterpriseDemo # 創(chuàng)建解決方案文件 dotnet new sln -n EnterpriseDemo # 創(chuàng)建各個項目 dotnet new webapi -n EnterpriseDemo.API -f net8.0 --no-https -o src/EnterpriseDemo.API dotnet new classlib -n EnterpriseDemo.Core -f net8.0 -o src/EnterpriseDemo.Core dotnet new classlib -n EnterpriseDemo.Application -f net8.0 -o src/EnterpriseDemo.Application dotnet new classlib -n EnterpriseDemo.Infrastructure -f net8.0 -o src/EnterpriseDemo.Infrastructure dotnet new xunit -n EnterpriseDemo.Tests -f net8.0 -o tests/EnterpriseDemo.Tests # 將項目添加到解決方案 dotnet sln add src/EnterpriseDemo.API/EnterpriseDemo.API.csproj dotnet sln add src/EnterpriseDemo.Core/EnterpriseDemo.Core.csproj dotnet sln add src/EnterpriseDemo.Application/EnterpriseDemo.Application.csproj dotnet sln add src/EnterpriseDemo.Infrastructure/EnterpriseDemo.Infrastructure.csproj dotnet sln add tests/EnterpriseDemo.Tests/EnterpriseDemo.Tests.csproj3.3 配置項目依賴關(guān)系這是架構(gòu)清晰的關(guān)鍵一步依賴必須單向流動高層模塊不依賴低層模塊細(xì)節(jié)。# API 層依賴 Application 和 Infrastructure dotnet add src/EnterpriseDemo.API/EnterpriseDemo.API.csproj reference src/EnterpriseDemo.Application/EnterpriseDemo.Application.csproj dotnet add src/EnterpriseDemo.API/EnterpriseDemo.API.csproj reference src/EnterpriseDemo.Infrastructure/EnterpriseDemo.Infrastructure.csproj # Application 層依賴 Core dotnet add src/EnterpriseDemo.Application/EnterpriseDemo.Application.csproj reference src/EnterpriseDemo.Core/EnterpriseDemo.Core.csproj # Infrastructure 層依賴 Core 和 Application (為實現(xiàn)Application中定義的接口) dotnet add src/EnterpriseDemo.Infrastructure/EnterpriseDemo.Infrastructure.csproj reference src/EnterpriseDemo.Core/EnterpriseDemo.Core.csproj dotnet add src/EnterpriseDemo.Infrastructure/EnterpriseDemo.Infrastructure.csproj reference src/EnterpriseDemo.Application/EnterpriseDemo.Application.csproj # Tests 項目依賴所有需要測試的項目 dotnet add tests/EnterpriseDemo.Tests/EnterpriseDemo.Tests.csproj reference src/EnterpriseDemo.API/EnterpriseDemo.API.csproj dotnet add tests/EnterpriseDemo.Tests/EnterpriseDemo.Tests.csproj reference src/EnterpriseDemo.Application/EnterpriseDemo.Application.csproj dotnet add tests/EnterpriseDemo.Tests/EnterpriseDemo.Tests.csproj reference src/EnterpriseDemo.Infrastructure/EnterpriseDemo.Infrastructure.csproj4. 核心層 (Core)定義領(lǐng)域模型與契約Core 項目應(yīng)該保持“純凈”不依賴任何其他項目。它包含系統(tǒng)的核心概念。4.1 定義領(lǐng)域?qū)嶓w在EnterpriseDemo.Core/Entities/文件夾下創(chuàng)建兩個實體類Product.cs和User.cs。// 文件src/EnterpriseDemo.Core/Entities/Product.cs using System; using System.ComponentModel.DataAnnotations; namespace EnterpriseDemo.Core.Entities { public class Product { [Key] public int Id { get; set; } [Required] [MaxLength(100)] public string Name { get; set; } string.Empty; [MaxLength(500)] public string? Description { get; set; } [Range(0, double.MaxValue)] public decimal Price { get; set; } public int StockQuantity { get; set; } public DateTime CreatedAt { get; set; } DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } } }// 文件src/EnterpriseDemo.Core/Entities/User.cs using System; using System.ComponentModel.DataAnnotations; namespace EnterpriseDemo.Core.Entities { public class User { [Key] public int Id { get; set; } [Required] [MaxLength(50)] public string Username { get; set; } string.Empty; [Required] [EmailAddress] [MaxLength(100)] public string Email { get; set; } string.Empty; // 注意實際項目中密碼不應(yīng)以明文存儲這里僅為演示。 // 應(yīng)使用哈希加鹽處理如 Identity 的 PasswordHasher。 [Required] public string PasswordHash { get; set; } string.Empty; public bool IsActive { get; set; } true; public DateTime CreatedAt { get; set; } DateTime.UtcNow; public DateTime? LastLoginAt { get; set; } } }4.2 定義數(shù)據(jù)訪問接口倉儲模式倉儲模式抽象了數(shù)據(jù)訪問邏輯。我們在Core層定義接口在Infrastructure層實現(xiàn)。在EnterpriseDemo.Core/Interfaces/下創(chuàng)建通用倉儲接口和具體實體的倉儲接口。// 文件src/EnterpriseDemo.Core/Interfaces/IGenericRepository.cs using System.Linq.Expressions; namespace EnterpriseDemo.Core.Interfaces { public interface IGenericRepositoryT where T : class { TaskT? GetByIdAsync(int id); TaskIEnumerableT GetAllAsync(); TaskIEnumerableT FindAsync(ExpressionFuncT, bool predicate); TaskT AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(T entity); Taskbool ExistsAsync(int id); } }// 文件src/EnterpriseDemo.Core/Interfaces/IProductRepository.cs using EnterpriseDemo.Core.Entities; namespace EnterpriseDemo.Core.Interfaces { public interface IProductRepository : IGenericRepositoryProduct { // 可以定義Product特有的數(shù)據(jù)訪問方法 TaskIEnumerableProduct GetProductsByPriceRangeAsync(decimal minPrice, decimal maxPrice); } }同理創(chuàng)建IUserRepository.cs。這樣業(yè)務(wù)邏輯層Application只依賴于這些接口而不關(guān)心底層是用Entity Framework、Dapper還是別的什么實現(xiàn)的。5. 基礎(chǔ)設(shè)施層 (Infrastructure)實現(xiàn)數(shù)據(jù)持久化這一層負(fù)責(zé)實現(xiàn)Core層定義的接口并處理與外部資源數(shù)據(jù)庫、文件、API等的交互。5.1 添加 EF Core 包并配置 DbContext首先為EnterpriseDemo.Infrastructure項目添加必要的 NuGet 包。cd src/EnterpriseDemo.Infrastructure dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools # 如果使用SQLite # dotnet add package Microsoft.EntityFrameworkCore.Sqlite然后創(chuàng)建數(shù)據(jù)庫上下文AppDbContext.cs。// 文件src/EnterpriseDemo.Infrastructure/Data/AppDbContext.cs using EnterpriseDemo.Core.Entities; using Microsoft.EntityFrameworkCore; namespace EnterpriseDemo.Infrastructure.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetProduct Products { get; set; } public DbSetUser Users { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 這里可以配置實體關(guān)系、索引、種子數(shù)據(jù)等 modelBuilder.EntityProduct(entity { entity.HasIndex(p p.Name).IsUnique(); // 產(chǎn)品名稱唯一索引 entity.Property(p p.Price).HasPrecision(18, 2); // 價格精度 }); modelBuilder.EntityUser(entity { entity.HasIndex(u u.Username).IsUnique(); entity.HasIndex(u u.Email).IsUnique(); }); // 種子數(shù)據(jù) modelBuilder.EntityProduct().HasData( new Product { Id 1, Name 示例產(chǎn)品A, Description 這是一個種子產(chǎn)品, Price 99.99m, StockQuantity 100 }, new Product { Id 2, Name 示例產(chǎn)品B, Description 另一個種子產(chǎn)品, Price 199.99m, StockQuantity 50 } ); } } }5.2 實現(xiàn)倉儲類在EnterpriseDemo.Infrastructure/Repositories/下實現(xiàn)具體的倉儲。// 文件src/EnterpriseDemo.Infrastructure/Repositories/GenericRepository.cs using EnterpriseDemo.Core.Interfaces; using EnterpriseDemo.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; namespace EnterpriseDemo.Infrastructure.Repositories { public class GenericRepositoryT : IGenericRepositoryT where T : class { protected readonly AppDbContext _context; protected readonly DbSetT _dbSet; public GenericRepository(AppDbContext context) { _context context; _dbSet context.SetT(); } public virtual async TaskT? GetByIdAsync(int id) { return await _dbSet.FindAsync(id); } public virtual async TaskIEnumerableT GetAllAsync() { return await _dbSet.ToListAsync(); } public virtual async TaskIEnumerableT FindAsync(ExpressionFuncT, bool predicate) { return await _dbSet.Where(predicate).ToListAsync(); } public virtual async TaskT AddAsync(T entity) { await _dbSet.AddAsync(entity); await _context.SaveChangesAsync(); return entity; } public virtual async Task UpdateAsync(T entity) { _dbSet.Update(entity); await _context.SaveChangesAsync(); } public virtual async Task DeleteAsync(T entity) { _dbSet.Remove(entity); await _context.SaveChangesAsync(); } public virtual async Taskbool ExistsAsync(int id) { var entity await GetByIdAsync(id); return entity ! null; } } }// 文件src/EnterpriseDemo.Infrastructure/Repositories/ProductRepository.cs using EnterpriseDemo.Core.Entities; using EnterpriseDemo.Core.Interfaces; using EnterpriseDemo.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace EnterpriseDemo.Infrastructure.Repositories { public class ProductRepository : GenericRepositoryProduct, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public async TaskIEnumerableProduct GetProductsByPriceRangeAsync(decimal minPrice, decimal maxPrice) { return await _context.Products .Where(p p.Price minPrice p.Price maxPrice) .OrderBy(p p.Price) .ToListAsync(); } } }UserRepository的實現(xiàn)類似。5.3 配置依賴注入Service Extensions為了保持Program.cs的整潔我們創(chuàng)建一個擴(kuò)展方法來集中注冊Infrastructure層的服務(wù)。// 文件src/EnterpriseDemo.Infrastructure/DependencyInjection.cs using EnterpriseDemo.Core.Interfaces; using EnterpriseDemo.Infrastructure.Data; using EnterpriseDemo.Infrastructure.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace EnterpriseDemo.Infrastructure { public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { // 配置數(shù)據(jù)庫上下文 services.AddDbContextAppDbContext(options options.UseSqlServer( configuration.GetConnectionString(DefaultConnection), b b.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName))); // 重要指定遷移程序集 // 注冊倉儲 services.AddScoped(typeof(IGenericRepository), typeof(GenericRepository)); services.AddScopedIProductRepository, ProductRepository(); services.AddScopedIUserRepository, UserRepository(); return services; } } }6. 應(yīng)用層 (Application)實現(xiàn)業(yè)務(wù)邏輯Application層是系統(tǒng)的核心它包含業(yè)務(wù)規(guī)則、工作流和用例。它依賴于Core層的接口并通過Infrastructure層注入的具體實現(xiàn)來操作數(shù)據(jù)。6.1 定義數(shù)據(jù)傳輸對象 (DTOs)Controller不應(yīng)直接接收或返回實體對象應(yīng)使用DTO。在EnterpriseDemo.Application/DTOs/下創(chuàng)建。// 文件src/EnterpriseDemo.Application/DTOs/ProductDto.cs using System.ComponentModel.DataAnnotations; namespace EnterpriseDemo.Application.DTOs { public class ProductDto { public int Id { get; set; } [Required] [StringLength(100)] public string Name { get; set; } string.Empty; [StringLength(500)] public string? Description { get; set; } [Range(0.01, double.MaxValue)] public decimal Price { get; set; } [Range(0, int.MaxValue)] public int StockQuantity { get; set; } } public class CreateProductDto { [Required] [StringLength(100)] public string Name { get; set; } string.Empty; [StringLength(500)] public string? Description { get; set; } [Range(0.01, double.MaxValue)] public decimal Price { get; set; } [Range(0, int.MaxValue)] public int StockQuantity { get; set; } } public class UpdateProductDto : CreateProductDto { // 更新DTO可能包含ID或其他標(biāo)識這里繼承自Create } }6.2 定義服務(wù)接口與實現(xiàn)在EnterpriseDemo.Application/Interfaces/和EnterpriseDemo.Application/Services/下創(chuàng)建。// 文件src/EnterpriseDemo.Application/Interfaces/IProductService.cs using EnterpriseDemo.Application.DTOs; namespace EnterpriseDemo.Application.Interfaces { public interface IProductService { TaskIEnumerableProductDto GetAllProductsAsync(); TaskProductDto? GetProductByIdAsync(int id); TaskProductDto CreateProductAsync(CreateProductDto createProductDto); Task UpdateProductAsync(int id, UpdateProductDto updateProductDto); Task DeleteProductAsync(int id); TaskIEnumerableProductDto GetProductsByPriceRangeAsync(decimal minPrice, decimal maxPrice); } }// 文件src/EnterpriseDemo.Application/Services/ProductService.cs using AutoMapper; using EnterpriseDemo.Application.DTOs; using EnterpriseDemo.Application.Interfaces; using EnterpriseDemo.Core.Entities; using EnterpriseDemo.Core.Interfaces; using Microsoft.Extensions.Logging; namespace EnterpriseDemo.Application.Services { public class ProductService : IProductService { private readonly IProductRepository _productRepository; private readonly IMapper _mapper; private readonly ILoggerProductService _logger; public ProductService(IProductRepository productRepository, IMapper mapper, ILoggerProductService logger) { _productRepository productRepository; _mapper mapper; _logger logger; } public async TaskIEnumerableProductDto GetAllProductsAsync() { var products await _productRepository.GetAllAsync(); _logger.LogInformation(獲取了所有產(chǎn)品共 {Count} 條記錄, products.Count()); return _mapper.MapIEnumerableProductDto(products); } public async TaskProductDto? GetProductByIdAsync(int id) { var product await _productRepository.GetByIdAsync(id); if (product null) { _logger.LogWarning(未找到ID為 {ProductId} 的產(chǎn)品, id); return null; } return _mapper.MapProductDto(product); } public async TaskProductDto CreateProductAsync(CreateProductDto createProductDto) { var productEntity _mapper.MapProduct(createProductDto); productEntity.CreatedAt DateTime.UtcNow; var createdProduct await _productRepository.AddAsync(productEntity); _logger.LogInformation(創(chuàng)建了新產(chǎn)品ID: {ProductId}, 名稱: {ProductName}, createdProduct.Id, createdProduct.Name); return _mapper.MapProductDto(createdProduct); } public async Task UpdateProductAsync(int id, UpdateProductDto updateProductDto) { var productEntity await _productRepository.GetByIdAsync(id); if (productEntity null) { throw new KeyNotFoundException($未找到ID為 {id} 的產(chǎn)品); } _mapper.Map(updateProductDto, productEntity); productEntity.UpdatedAt DateTime.UtcNow; await _productRepository.UpdateAsync(productEntity); _logger.LogInformation(更新了產(chǎn)品ID: {ProductId}, id); } public async Task DeleteProductAsync(int id) { var productEntity await _productRepository.GetByIdAsync(id); if (productEntity null) { throw new KeyNotFoundException($未找到ID為 {id} 的產(chǎn)品); } await _productRepository.DeleteAsync(productEntity); _logger.LogInformation(刪除了產(chǎn)品ID: {ProductId}, id); } public async TaskIEnumerableProductDto GetProductsByPriceRangeAsync(decimal minPrice, decimal maxPrice) { var products await _productRepository.GetProductsByPriceRangeAsync(minPrice, maxPrice); return _mapper.MapIEnumerableProductDto(products); } } }注意這里使用了AutoMapper進(jìn)行對象映射。需要為Application項目添加AutoMapper.Extensions.Microsoft.DependencyInjection包。6.3 配置AutoMapper Profile在EnterpriseDemo.Application/Common/Mappings/下創(chuàng)建映射配置。// 文件src/EnterpriseDemo.Application/Common/Mappings/MappingProfile.cs using AutoMapper; using EnterpriseDemo.Application.DTOs; using EnterpriseDemo.Core.Entities; namespace EnterpriseDemo.Application.Common.Mappings { public class MappingProfile : Profile { public MappingProfile() { CreateMapProduct, ProductDto().ReverseMap(); CreateMapProduct, CreateProductDto().ReverseMap(); CreateMapProduct, UpdateProductDto().ReverseMap(); // 對于Update我們可能希望部分字段忽略可以更精細(xì)配置 CreateMapUpdateProductDto, Product() .ForAllMembers(opts opts.Condition((src, dest, srcMember) srcMember ! null)); } } }6.4 應(yīng)用層依賴注入配置同樣創(chuàng)建一個擴(kuò)展方法來注冊Application層的服務(wù)。// 文件src/EnterpriseDemo.Application/DependencyInjection.cs using EnterpriseDemo.Application.Common.Mappings; using EnterpriseDemo.Application.Interfaces; using EnterpriseDemo.Application.Services; using Microsoft.Extensions.DependencyInjection; using System.Reflection; namespace EnterpriseDemo.Application { public static class DependencyInjection { public static IServiceCollection AddApplication(this IServiceCollection services) { // 注冊AutoMapper從當(dāng)前程序集掃描Profile services.AddAutoMapper(Assembly.GetExecutingAssembly()); // 注冊應(yīng)用服務(wù) services.AddScopedIProductService, ProductService(); services.AddScopedIUserService, UserService(); // 假設(shè)有UserService return services; } } }7. 表現(xiàn)層 (API)構(gòu)建RESTful端點(diǎn)現(xiàn)在我們來到最外層構(gòu)建供客戶端調(diào)用的API。7.1 配置API項目首先為EnterpriseDemo.API項目添加對Microsoft.EntityFrameworkCore.Design包的引用用于遷移。cd src/EnterpriseDemo.API dotnet add package Microsoft.EntityFrameworkCore.Design然后修改appsettings.json添加數(shù)據(jù)庫連接字符串。// 文件src/EnterpriseDemo.API/appsettings.json { Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, ConnectionStrings: { DefaultConnection: Server(localdb)\\mssqllocaldb;DatabaseEnterpriseDemoDb;Trusted_ConnectionTrue;MultipleActiveResultSetstrue;TrustServerCertificateTrue }, AllowedHosts: * }7.2 配置Program.cs這是應(yīng)用的入口負(fù)責(zé)組裝所有部件。// 文件src/EnterpriseDemo.API/Program.cs using EnterpriseDemo.Application; using EnterpriseDemo.Infrastructure; var builder WebApplication.CreateBuilder(args); // 添加服務(wù)到容器 builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // 添加Swagger // 注冊我們自定義的各層服務(wù) builder.Services.AddApplication(); builder.Services.AddInfrastructure(builder.Configuration); var app builder.Build(); // 配置HTTP請求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); // 確保數(shù)據(jù)庫被創(chuàng)建并應(yīng)用遷移僅用于開發(fā)環(huán)境 using (var scope app.Services.CreateScope()) { var dbContext scope.ServiceProvider.GetRequiredServiceInfrastructure.Data.AppDbContext(); dbContext.Database.EnsureCreated(); // 或使用 dbContext.Database.Migrate() 如果使用了遷移 } app.Run();7.3 創(chuàng)建Product控制器在Controllers文件夾下創(chuàng)建ProductsController.cs。// 文件src/EnterpriseDemo.API/Controllers/ProductsController.cs using EnterpriseDemo.Application.DTOs; using EnterpriseDemo.Application.Interfaces; using Microsoft.AspNetCore.Mvc; namespace EnterpriseDemo.API.Controllers { [Route(api/[controller])] [ApiController] public class ProductsController : ControllerBase { private readonly IProductService _productService; public ProductsController(IProductService productService) { _productService productService; } // GET: api/products [HttpGet] public async TaskActionResultIEnumerableProductDto GetProducts() { var products await _productService.GetAllProductsAsync(); return Ok(products); } // GET: api/products/5 [HttpGet({id})] public async TaskActionResultProductDto GetProduct(int id) { var product await _productService.GetProductByIdAsync(id); if (product null) { return NotFound(); } return Ok(product); } // POST: api/products [HttpPost] public async TaskActionResultProductDto CreateProduct(CreateProductDto createProductDto) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var createdProduct await _productService.CreateProductAsync(createProductDto); return CreatedAtAction(nameof(GetProduct), new { id createdProduct.Id }, createdProduct); } // PUT: api/products/5 [HttpPut({id})] public async TaskIActionResult UpdateProduct(int id, UpdateProductDto updateProductDto) { if (id ! updateProductDto.Id) // 假設(shè)UpdateProductDto包含Id { return BadRequest(ID不匹配); } if (!ModelState.IsValid) { return BadRequest(ModelState); } try { await _productService.UpdateProductAsync(id, updateProductDto); } catch (KeyNotFoundException) { return NotFound(); } return NoContent(); } // DELETE: api/products/5 [HttpDelete({id})] public async TaskIActionResult DeleteProduct(int id) { try { await _productService.DeleteProductAsync(id); } catch (KeyNotFoundException) { return NotFound(); } return NoContent(); } // GET: api/products/price-range?min10max100 [HttpGet(price-range)] public async TaskActionResultIEnumerableProductDto GetProductsByPriceRange([FromQuery] decimal min, [FromQuery] decimal max) { if (min max) { return BadRequest(最低價格不能高于最高價格); } var products await _productService.GetProductsByPriceRangeAsync(min, max); return Ok(products); } } }8. 運(yùn)行與驗證8.1 運(yùn)行項目在終端中導(dǎo)航到src/EnterpriseDemo.API目錄。運(yùn)行dotnet run??刂婆_會輸出應(yīng)用監(jiān)聽的地址通常是https://localhost:5001和http://localhost:5000。8.2 使用Swagger UI測試API在瀏覽器中打開https://localhost:5001/swagger(或?qū)?yīng)的HTTP地址)。你將看到自動生成的API文檔列出了Products的所有端點(diǎn)。嘗試執(zhí)行以下操作GET /api/products: 應(yīng)返回兩個種子產(chǎn)品。POST /api/products: 點(diǎn)擊“Try it out”填入JSON數(shù)據(jù)創(chuàng)建新產(chǎn)品。{ name: 新測試產(chǎn)品, description: 通過API創(chuàng)建, price: 299.99, stockQuantity: 10 }GET /api/products/{id}: 使用新創(chuàng)建產(chǎn)品的ID獲取它。PUT /api/products/{id}: 更新產(chǎn)品信息。DELETE /api/products/{id}: 刪除產(chǎn)品。GET /api/products/price-range?min50max200: 查詢價格范圍內(nèi)的產(chǎn)品。8.3 驗證數(shù)據(jù)庫可以使用SQL Server Object Explorer(VS) 或Azure Data Studio等工具連接到(localdb)\mssqllocaldb查看EnterpriseDemoDb數(shù)據(jù)庫中的Products和Users表確認(rèn)數(shù)據(jù)已持久化。9. 常見問題與排查思路問題現(xiàn)象可能原因排查方式解決方案啟動時報錯無法找到AppDbContext的構(gòu)造函數(shù)1.AppDbContext未在Program.cs中通過AddDbContext注冊。2. 連接字符串配置錯誤。1. 檢查Program.cs中是否調(diào)用了AddInfrastructure。2. 檢查appsettings.json中的ConnectionStrings節(jié)點(diǎn)。1. 確保services.AddInfrastructure(configuration);被調(diào)用。2. 確認(rèn)連接字符串格式正確數(shù)據(jù)庫實例存在。運(yùn)行遷移命令dotnet ef migrations add InitialCreate失敗1. 未在啟動項目API中安裝Microsoft.EntityFrameworkCore.Design。2. 未指定啟動項目和DbContext所在項目。查看錯誤信息通常提示“No DbContext was found”。1. 確保在API項目中安裝了Microsoft.EntityFrameworkCore.Design包。2. 使用完整命令dotnet ef migrations add InitialCreate -s ../EnterpriseDemo.API -p ../EnterpriseDemo.InfrastructureSwagger頁面能打開但調(diào)用API返回4041. 控制器路由配置錯誤。2. 請求的HTTP方法或URL不正確。1. 檢查控制器上的[Route(api/[controller])]屬性。2. 在Swagger UI中查看每個端點(diǎn)的完整路徑。1. 確保控制器繼承自ControllerBase并具有[ApiController]屬性。2. 嚴(yán)格按照Swagger顯示的URL和參數(shù)格式調(diào)用。AutoMapper映射失敗缺少類型映射配置1.MappingProfile未正確注冊到AutoMapper。2. DTO和Entity的屬性名或類型不匹配。1. 檢查AddApplication方法中是否調(diào)用了AddAutoMapper。2. 在服務(wù)中注入IMapper并單步調(diào)試查看映射異常信息。1. 確保AddAutoMapper(Assembly.GetExecutingAssembly())能掃描到包含MappingProfile的程序集。2. 在MappingProfile中為復(fù)雜的映射關(guān)系添加自定義CreateMap配置。依賴注入錯誤無法解析服務(wù)1. 服務(wù)如IProductService未在DI容器中注冊。2. 服務(wù)生命周期配置錯誤如Scoped服務(wù)在Singleton中注入。查看啟動時的異常堆棧信息會明確指出哪個服務(wù)無法解析。1. 檢查IProductService和ProductService是否在AddApplication中通過AddScoped注冊。2. 確保服務(wù)生命周期匹配。通常倉儲和服務(wù)使用Scoped。10. 最佳實踐與工程建議異步編程 如上所示全程使用async/await避免阻塞線程提高Web應(yīng)用的并發(fā)能力。日志記錄 在服務(wù)層使用ILogger記錄關(guān)鍵操作和異常便于生產(chǎn)環(huán)境排查問題。避免在Controller中記錄過多業(yè)務(wù)日志。異常處理 在Controller中進(jìn)行基本的模型驗證和異常捕獲返回合適的HTTP狀態(tài)碼如404、400。更復(fù)雜的業(yè)務(wù)異常可以考慮使用自定義異常和全局異常過濾器。輸入驗證 除了Controller的[ApiController]自動模型驗證在DTO上使用數(shù)據(jù)注解如[Required],[StringLength]進(jìn)行聲明式驗證。復(fù)雜規(guī)則應(yīng)在服務(wù)層實現(xiàn)??缬蛘埱?(CORS) 如果前端獨(dú)立部署需要在Program.cs中配置CORS策略。環(huán)境配置 使用appsettings.Development.json和appsettings.Production.json管理不同環(huán)境的配置如數(shù)據(jù)庫連接字符串、日志級別。單元測試 為服務(wù)層編寫單元測試使用xUnit和Moq模擬倉儲依賴確保業(yè)務(wù)邏輯正確性。遷移管理 對于生產(chǎn)環(huán)境使用EF Core的代碼遷移 (dotnet ef migrations) 來管理數(shù)據(jù)庫架構(gòu)變更而不是EnsureCreated。API版本控制 當(dāng)API需要重大變更時考慮引入API版本控制如Microsoft.AspNetCore.Mvc.Versioning。性能考量 對于大型數(shù)據(jù)集在倉儲層實現(xiàn)分頁查詢避免一次性加載所有數(shù)據(jù)。考慮使用AsNoTracking()進(jìn)行只讀查詢以提升性能。至此你已經(jīng)擁有了一個結(jié)構(gòu)清晰、職責(zé)分明、可測試、可擴(kuò)展的C#企業(yè)級項目骨架。這個項目模板清晰地展示了分層架構(gòu)、依賴注入、倉儲模式、DTO映射等核心概念是如何協(xié)同工作的。你可以在此基礎(chǔ)上輕松地添加新的實體、業(yè)務(wù)邏輯和API端點(diǎn)例如實現(xiàn)完整的用戶認(rèn)證授權(quán)JWT、更復(fù)雜的查詢過濾、文件上傳、緩存集成、消息隊列等高級功能。真正的企業(yè)級項目正是在這樣健壯的基礎(chǔ)上通過不斷解決具體的業(yè)務(wù)需求而演化出來的。建議你將此項目作為模板保存并在未來的開發(fā)中反復(fù)實踐和優(yōu)化這些模式。

相關(guān)新聞

Qt C++ QListWidgetItem 核心用法:從數(shù)據(jù)綁定到性能優(yōu)化

Qt C++ QListWidgetItem 核心用法:從數(shù)據(jù)綁定到性能優(yōu)化

這次我們來看 Qt C 中一個非常核心的 GUI 組件類:QListWidgetItem。如果你正在用 Qt 開發(fā)桌面應(yīng)用,并且需要處理列表、圖標(biāo)、復(fù)選框、自定義數(shù)據(jù)這些功能,那么這個類就是你繞不開的基石。它不僅僅是QListWidget里一個簡單的條目,更…

2026/8/2 2:04:35 閱讀更多
CTFshow XSS靶場實戰(zhàn):5類Payload繞過技巧與防御原理詳解

CTFshow XSS靶場實戰(zhàn):5類Payload繞過技巧與防御原理詳解

1. 項目概述:從CTFshow XSS靶場到實戰(zhàn)Payload庫最近在帶新人過CTFshow的Web入門系列,特別是XSS(跨站腳本攻擊)相關(guān)的關(guān)卡,發(fā)現(xiàn)很多朋友卡在了各種過濾規(guī)則上。這些關(guān)卡設(shè)計得很有意思,它們不是簡單地讓你彈…

2026/8/2 1:54:35 閱讀更多
游戲模組制作實戰(zhàn):從腳本修改到武器配置的完整指南

游戲模組制作實戰(zhàn):從腳本修改到武器配置的完整指南

在游戲開發(fā)或模組制作領(lǐng)域,為經(jīng)典游戲創(chuàng)作新的劇本、關(guān)卡或角色,是許多資深玩家和技術(shù)愛好者深入探索游戲機(jī)制、實現(xiàn)個人創(chuàng)意的常見方式。這個過程不僅需要對游戲引擎和資源文件有深刻理解,還需要具備一定的腳本編寫、關(guān)卡設(shè)計和平衡性調(diào)整能…

2026/8/2 3:14:38 閱讀更多
【計算機(jī)工具類-CI和CD工具Skills】aws-serverless 技能

【計算機(jī)工具類-CI和CD工具Skills】aws-serverless 技能

在AWS上構(gòu)建生產(chǎn)就緒的無服務(wù)器應(yīng)用程序的專門技能。涵蓋Lambda函數(shù)、API Gateway、DynamoDB、SQS/SNS事件驅(qū)動模式、SAM/CDK部署和冷啟動優(yōu)化。 技能概述 aws-serverless 技能提供了在AWS上構(gòu)建生產(chǎn)就緒的無服務(wù)器應(yīng)用程序的專家模式和最佳實踐。它涵蓋了Lambda函數(shù)、API Ga…

2026/8/2 3:14:38 閱讀更多
密碼正則驗證全鏈路實踐:從安全原理到前后端實現(xiàn)

密碼正則驗證全鏈路實踐:從安全原理到前后端實現(xiàn)

1. 項目概述:為什么密碼驗證遠(yuǎn)不止“8位”這么簡單?“密碼必須包含大小寫字母、數(shù)字、特殊字符,且至少8位”——這個規(guī)則,幾乎每個上網(wǎng)沖浪的人都見過。乍一看,它像是一道簡單的數(shù)學(xué)題,組合一下字符集&…

2026/8/2 3:14:38 閱讀更多
Java后端集成EasyNVR視頻平臺:SpringBoot對接API實戰(zhàn)與優(yōu)化

Java后端集成EasyNVR視頻平臺:SpringBoot對接API實戰(zhàn)與優(yōu)化

1. 項目概述與背景最近在做一個安防監(jiān)控相關(guān)的項目,需要把分散在不同地點(diǎn)的網(wǎng)絡(luò)攝像頭視頻流集中管理起來,并且要能通過我們自己的后臺系統(tǒng)進(jìn)行實時預(yù)覽、錄像回放和設(shè)備控制。市面上成熟的視頻平臺不少,但要么太貴,要么二次開發(fā)接…

2026/8/2 3:14:38 閱讀更多
3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南 【免費(fèi)下載鏈接】GetQzonehistory 獲取QQ空間發(fā)布的歷史說說 項目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想過,那些年發(fā)過的QQ空間說說,那些記錄青春的文字…

2026/8/2 0:04:01 閱讀更多
3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南 【免費(fèi)下載鏈接】GetQzonehistory 獲取QQ空間發(fā)布的歷史說說 項目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想過,那些年發(fā)過的QQ空間說說,那些記錄青春的文字…

2026/8/2 0:04:01 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應(yīng)用材料(Applied Materials)公司生產(chǎn)的一款用于半導(dǎo)體設(shè)備的I/O信號分配電路板。該型號(0100-02186)的核心特點(diǎn)如下:專用于Endura等半導(dǎo)體工藝腔室。集成信號路由與分配功能。連接控制…

2026/8/2 2:51:21 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機(jī)是日本日清(Nissei)品牌的一款工業(yè)用三相異步電機(jī),適用于自動化設(shè)備及通用機(jī)械驅(qū)動。該型號(FFMN-32L-10-T0 40AX)的核心特點(diǎn)如下:三相交流異步電動機(jī)。額定…

2026/8/2 2:52:49 閱讀更多