如何在C# .NET Core 2.0 MVC控制器中复用代码并遵循DRY原则
咱们先看看你给出的这段获取部门列表的代码,里面重复的部分其实挺典型的——每次调用API都要创建HttpClient、发请求、判断状态码、反序列化,要是每个控制器方法都这么写,代码冗余度会非常高。下面给你几个落地性强的方案,帮你把代码精简下来,严格遵循DRY(Don't Repeat Yourself)原则:
1. 抽离通用API请求服务
把HttpClient的初始化、请求发送、响应处理这些通用逻辑封装成独立服务,所有控制器通过依赖注入使用,彻底告别重复的模板代码。
步骤示例:
首先定义一个API客户端接口:
public interface IHttpApiClient { Task<T> GetAsync<T>(string endpoint); // 按需扩展PostAsync、PutAsync等其他HTTP方法 }
然后实现这个接口,统一处理请求逻辑:
public class HttpApiClient : IHttpApiClient { private readonly IHttpClientFactory _httpClientFactory; public HttpApiClient(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<T> GetAsync<T>(string endpoint) { // 使用命名客户端(可在Startup中配置基础地址、请求头等) var client = _httpClientFactory.CreateClient("YourApiClient"); var response = await client.GetAsync(endpoint); if (!response.IsSuccessStatusCode) { // 统一处理请求失败场景,比如抛自定义异常或返回默认值 throw new HttpRequestException($"API请求失败,状态码:{response.StatusCode}"); } var content = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize<T>(content); } }
在Startup.cs中注册服务:
public void ConfigureServices(IServiceCollection services) { // 配置命名HttpClient services.AddHttpClient("YourApiClient", client => { client.BaseAddress = new Uri("https://your-api-base-url/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); // 注册API客户端服务 services.AddScoped<IHttpApiClient, HttpApiClient>(); // 其他服务注册... }
现在控制器里的代码就变得异常简洁:
public class DepartmentController : Controller { private readonly IHttpApiClient _apiClient; public DepartmentController(IHttpApiClient apiClient) { _apiClient = apiClient; } [HttpGet] public async Task<IActionResult> Department() { try { var departments = await _apiClient.GetAsync<List<Department>>("api/Department"); return View(departments); } catch (HttpRequestException ex) { // 这里可以统一处理错误,比如返回错误视图 TempData["Error"] = ex.Message; return View("Error"); } } }
2. 创建基类控制器统一逻辑
如果多个控制器都有类似的错误处理、视图返回逻辑,可以把这些共性逻辑放到基类控制器中,让业务控制器继承它,进一步简化代码。
基类示例:
public class BaseController : Controller { protected readonly IHttpApiClient _apiClient; public BaseController(IHttpApiClient apiClient) { _apiClient = apiClient; } // 封装通用的API请求处理逻辑 protected async Task<IActionResult> HandleApiRequestAsync<T>(string endpoint, string successView = null) { try { var data = await _apiClient.GetAsync<T>(endpoint); return string.IsNullOrEmpty(successView) ? View(data) : View(successView, data); } catch (HttpRequestException ex) { TempData["ErrorMessage"] = $"获取数据失败:{ex.Message}"; return View("Error"); } } }
业务控制器继承基类后,代码更精简:
public class DepartmentController : BaseController { public DepartmentController(IHttpApiClient apiClient) : base(apiClient) { } [HttpGet] public async Task<IActionResult> Department() { return await HandleApiRequestAsync<List<Department>>("api/Department"); } }
后续如果要修改错误处理逻辑,只需要改动基类即可,不用逐个修改业务控制器,维护成本大大降低。
3. EF场景下用仓储模式封装数据操作
如果是使用EF Core操作数据库的重复代码(比如获取列表、增删改查),仓储模式是绝佳的选择——把数据访问逻辑封装到仓储类中,控制器只负责调用仓储方法,不用重复写DbContext相关代码。
仓储示例:
首先定义仓储接口:
public interface IDepartmentRepository { Task<List<Department>> GetAllDepartmentsAsync(); // 按需扩展GetDepartmentByIdAsync、AddDepartmentAsync等方法 }
实现仓储类:
public class DepartmentRepository : IDepartmentRepository { private readonly AppDbContext _dbContext; public DepartmentRepository(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Department>> GetAllDepartmentsAsync() { return await _dbContext.Departments.ToListAsync(); } }
注册仓储服务:
services.AddScoped<IDepartmentRepository, DepartmentRepository>();
控制器中直接调用仓储:
public class DepartmentController : Controller { private readonly IDepartmentRepository _departmentRepo; public DepartmentController(IDepartmentRepository departmentRepo) { _departmentRepo = departmentRepo; } [HttpGet] public async Task<IActionResult> Department() { var departments = await _departmentRepo.GetAllDepartmentsAsync(); return View(departments); } }
这样数据访问逻辑完全和控制器解耦,复用性极强,而且后续更换数据访问技术(比如从EF换成Dapper)也不会影响控制器代码。
4. 用扩展方法快速简化重复代码
如果不想创建太多服务和类,扩展方法是轻量且高效的选择——给HttpClient或其他类扩展方法,封装重复的操作逻辑。
扩展方法示例:
public static class HttpClientExtensions { public static async Task<T> GetAndDeserializeAsync<T>(this HttpClient client, string endpoint) { var response = await client.GetAsync(endpoint); // 失败时直接抛出HttpRequestException response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize<T>(content); } }
然后控制器里的代码可以简化成:
[HttpGet] public async Task<IActionResult> Department() { using (var client = await _apiHttpClient.GetHttpClientAsync()) { try { var departments = await client.GetAndDeserializeAsync<List<Department>>("api/Department"); return View(departments); } catch (HttpRequestException ex) { return View("Error", ex.Message); } } }
这个方案适合快速优化现有代码,不需要大改架构。
内容的提问来源于stack exchange,提问作者alhpe

