You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C# .NET Core 2.0 MVC控制器中复用代码并遵循DRY原则

在.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:38:26