如何重构ASP.NET Core控制器中重复的日志代码,提升可复用性
代码重构方案
针对你需要复用控制器日志逻辑的需求,有两种常用的落地方案,可根据你的实际使用场景选择:
方案一:抽取通用控制器基类(适配需要在Action中使用userId、path打自定义日志的场景)
这是改动最小的方案,所有需要用到该日志逻辑的控制器只要继承该基类即可,无需重复编写初始化逻辑:
// 先定义通用基类 public class BaseController<T> : Controller where T : Controller { protected readonly ILogger<T> Logger; protected string UserId; protected string RequestPath; public BaseController(ILogger<T> logger) { Logger = logger; } public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); UserId = User.FindFirstValue(ClaimTypes.NameIdentifier); RequestPath = $"{context.HttpContext.Request.Path.Value}[{context.HttpContext.Request.Method}]"; Logger.LogDebug($"{RequestPath} started. User(id): {UserId}."); } } // 业务控制器继承使用 public class HomeController : BaseController<HomeController> { public HomeController(ILogger<HomeController> logger) : base(logger) { } // 你的业务Action public IActionResult Approve() { // 直接用基类的属性打日志即可,和你之前的使用习惯一致 Logger.LogInformation($"{RequestPath} Was approved for User(id): {UserId}."); return Ok(); } } public class AccountController : BaseController<AccountController> { public AccountController(ILogger<AccountController> logger) : base(logger) { } // 其他业务逻辑 }
方案二:使用Action过滤器(适配仅需要统一打入口日志,不需要在Action中访问userId、path的场景)
该方案完全将日志逻辑和控制器解耦,符合单一职责原则,不需要修改现有控制器的继承结构:
// 定义日志过滤器 public class RequestLoggingFilter : IActionFilter { private readonly ILogger<RequestLoggingFilter> _logger; public RequestLoggingFilter(ILogger<RequestLoggingFilter> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { var user = context.HttpContext.User; var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); var path = $"{context.HttpContext.Request.Path.Value}[{context.HttpContext.Request.Method}]"; _logger.LogDebug($"{path} started. User(id): {userId}."); // 如果还是需要在Action中访问这两个值,可以存入HttpContext.Items context.HttpContext.Items["UserId"] = userId; context.HttpContext.Items["RequestPath"] = path; } public void OnActionExecuted(ActionExecutedContext context) { // 还可以在这里扩展Action执行完成的日志逻辑 } }
注册方式:
- 如果要全局生效:在Program.cs中添加
builder.Services.AddControllers(options => options.Filters.Add<RequestLoggingFilter>()); - 如果只要部分控制器生效:在对应控制器上加
[TypeFilter(typeof(RequestLoggingFilter))]特性即可
补充说明:原示例代码存在笔误,
HomeController的构造函数中注入的Logger泛型参数写成了AccountController,使用基类方案可以避免这类复制粘贴导致的错误。
内容的提问来源于stack exchange,提问作者Mister D
相关产品推荐
相关产品推荐

