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

ASP.NET Core控制器实现DRY原则求助:精简重复代码方案

ASP.NET Core控制器冗余问题的生产环境最佳实践

针对你提到的4个控制器Get方法中日志记录、返回状态判断等重复逻辑问题,以下是生产环境中经过验证的几种解决方案及选型建议:

1. 抽象基类(适用于控制器间通用逻辑高度一致的场景)

将重复逻辑封装到抽象基类中,子类控制器继承后直接复用方法,代码直观且易于维护。

实现示例

基类代码:

public abstract class BaseApiController : ControllerBase
{
    protected readonly ILogger _logger;

    public BaseApiController(ILogger logger)
    {
        _logger = logger;
    }

    // 封装通用的Get结果处理逻辑
    protected IActionResult HandleGetResult<T>(T result, string operationIdentifier)
    {
        // 返回状态判断
        if (result == null)
        {
            _logger.LogWarning("{Operation} 返回空结果", operationIdentifier);
            return NotFound();
        }

        // 结果日志记录
        _logger.LogInformation("{Operation} 执行成功,结果:{@Result}", operationIdentifier, result);
        return Ok(result);
    }
}

子类控制器调用:

public class ControllerA : BaseApiController
{
    private readonly IServiceA _serviceA;

    public ControllerA(ILogger<ControllerA> logger, IServiceA serviceA) 
        : base(logger)
    {
        _serviceA = serviceA;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var data = _serviceA.GetData();
        return HandleGetResult(data, $"{nameof(ControllerA)}.Get");
    }
}

优势与注意事项

  • 优势:子类代码简洁,逻辑集中,修改通用逻辑只需改动基类。
  • 注意:基类仅保留通用逻辑,避免过度膨胀;子类传入自身的Logger实例,确保日志类别准确。

2. 动作过滤器(适用于全局横切关注点场景)

利用ASP.NET Core的Action Filter实现AOP,将日志、状态判断等横切逻辑与控制器解耦,适合全局或批量控制器的逻辑复用。

实现示例

自定义过滤器:

public class GetResultProcessingFilter : ActionFilterAttribute
{
    private readonly ILogger<GetResultProcessingFilter> _logger;

    public GetResultProcessingFilter(ILogger<GetResultProcessingFilter> logger)
    {
        _logger = logger;
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        base.OnActionExecuted(context);

        var operationName = $"{context.Controller.GetType().Name}.{context.ActionDescriptor.ActionName}";

        // 处理返回结果
        if (context.Result is ObjectResult objectResult)
        {
            if (objectResult.Value == null)
            {
                _logger.LogWarning("{Operation} 返回空结果", operationName);
                context.Result = new NotFoundResult();
            }
            else
            {
                _logger.LogInformation("{Operation} 执行成功,结果:{@Result}", operationName, objectResult.Value);
            }
        }
        else if (context.Result is NotFoundResult)
        {
            _logger.LogWarning("{Operation} 返回NotFound状态", operationName);
        }
    }
}

注册与使用

  • 全局注册(所有控制器生效):
builder.Services.AddControllers(options =>
{
    options.Filters.Add<GetResultProcessingFilter>();
});
  • 局部使用(仅指定控制器/方法生效):
[GetResultProcessingFilter]
public class ControllerA : ControllerBase
{
    private readonly IServiceA _serviceA;

    public ControllerA(IServiceA serviceA)
    {
        _serviceA = serviceA;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var data = _serviceA.GetData();
        return Ok(data);
    }
}

优势与注意事项

  • 优势:完全解耦控制器与通用逻辑,可灵活控制作用范围,便于统一维护。
  • 注意:需覆盖所有可能的Result类型,避免逻辑遗漏;过滤器依赖注入需正确注册到服务容器。

3. 可注入辅助类(适用于跨场景逻辑复用)

将通用逻辑封装为独立服务,通过DI注入到控制器或其他组件中,适合需要在控制器之外复用的复杂逻辑。

实现示例

定义接口与实现类:

public interface IResultHandler
{
    IActionResult ProcessGetResult<T>(T result, string operationName, ILogger logger);
}

public class ResultHandler : IResultHandler
{
    public IActionResult ProcessGetResult<T>(T result, string operationName, ILogger logger)
    {
        if (result == null)
        {
            logger.LogWarning("{Operation} 返回空结果", operationName);
            return new NotFoundResult();
        }

        logger.LogInformation("{Operation} 执行成功,结果:{@Result}", operationName, result);
        return new OkObjectResult(result);
    }
}

注册服务:

builder.Services.AddScoped<IResultHandler, ResultHandler>();

控制器使用:

public class ControllerA : ControllerBase
{
    private readonly IServiceA _serviceA;
    private readonly IResultHandler _resultHandler;
    private readonly ILogger<ControllerA> _logger;

    public ControllerA(IServiceA serviceA, IResultHandler resultHandler, ILogger<ControllerA> logger)
    {
        _serviceA = serviceA;
        _resultHandler = resultHandler;
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var data = _serviceA.GetData();
        return _resultHandler.ProcessGetResult(data, $"{nameof(ControllerA)}.Get", _logger);
    }
}

优势与注意事项

  • 优势:逻辑独立,单元测试友好,可在服务层、其他处理类中复用。
  • 注意:控制器构造函数会增加依赖参数,需合理控制服务职责单一性。

生产环境选型建议

  • 若仅特定几个控制器的Get方法有重复逻辑:优先选择抽象基类,代码简洁易维护。
  • 若需全局批量控制器/动作复用相同逻辑:优先选择动作过滤器,解耦性强,符合AOP思想。
  • 若通用逻辑需要在控制器外复用:选择可注入辅助类,保证复用性与可测试性。

注:Controller扩展方法不推荐在生产环境大规模使用,因其静态特性导致依赖注入困难、单元测试不便,且逻辑分散不易维护。

内容的提问来源于stack exchange,提问作者cueball8

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 01:36:33