如何在Error类的扩展方法ToProblemDetails中添加日志记录?
问题描述
我在业务服务中使用OneOf<>区分联合类型实现多结果返回,定义了如下错误类:
// Errors public record Error(string Code, string Description); public sealed record NotDeleted(int Id, string EntityName) : Error("NotDeleted", $"{EntityName} with Id = {Id} cannot be deleted."); public sealed record NotFound(int Id, string EntityName) : Error("NotFound", $"{EntityName} with Id = {Id} cannot be found.");
业务服务类代码:
// Business Services public sealed class MovieService(AppDbContext db) { private readonly AppDbContext _db = db; public async Task<OneOf<NotFound, NotDeleted, Movie>> DeleteMovieAsync(int id, CancellationToken ct) { var movie = await _db.Movies .Include(m => m.Genres) .FirstOrDefaultAsync(m => m.Id == id, ct); if (movie is null) return new NotFound(id, nameof(Movie)); _db.Movies.Remove(movie); return await _db.SaveChangesAsync(ct) > 0 ? movie : new NotDeleted(id, nameof(Movie)); } }
业务服务注入到端点的代码:
// Endpoints public static class MovieEndpoints { public static void MapMovieEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("api/movies"); group.MapDelete("{id}", DeleteMovieAsync); } private static async Task<Results<ProblemHttpResult, Ok<MovieResponse>>> DeleteMovieAsync(int id, MovieService ms, CancellationToken ct) { var result = await ms.DeleteMovieAsync(id, ct); return result.Match<Results<ProblemHttpResult, Ok<MovieResponse>>>( notFoundError => notFoundError.ToProblemDetails(), notDeletedError => notDeletedError.ToProblemDetails(), movie => TypedResults.Ok(movie.MapToMovieResponse())); } }
我为Error类编写了生成ProblemDetails的扩展方法,现在需要在ToProblemDetails()内部标注位置添加日志记录,该如何实现?
// Mappers public static class ErrorMapper { private static ProblemHttpResult ToProblemDetails(Error error, int statusCode) { // Logging goes here! return TypedResults.Problem(new ProblemDetails { Title = error.Code, Detail = error.Description, Status = statusCode }); } public static ProblemHttpResult ToProblemDetails(this NotFound error) => ToProblemDetails(error, StatusCodes.Status404NotFound); public static ProblemHttpResult ToProblemDetails(this NotDeleted error) => ToProblemDetails(error, StatusCodes.Status500InternalServerError); }
解决方案
由于ErrorMapper是静态类,无法直接依赖注入ILogger,以下是三种可行实现方案:
方案一:将ILogger作为参数传入扩展方法
这是最符合依赖注入原则的方案,修改扩展方法让调用者传入ILogger实例:
- 更新
ErrorMapper类,添加ILogger参数:
// Mappers public static class ErrorMapper { private static ProblemHttpResult ToProblemDetails(Error error, int statusCode, ILogger logger) { // 根据错误状态码选择日志级别 if (statusCode == StatusCodes.Status404NotFound) { logger.LogWarning("Error: {Code} - {Description}", error.Code, error.Description); } else { logger.LogError("Error: {Code} - {Description}", error.Code, error.Description); } return TypedResults.Problem(new ProblemDetails { Title = error.Code, Detail = error.Description, Status = statusCode }); } public static ProblemHttpResult ToProblemDetails(this NotFound error, ILogger logger) => ToProblemDetails(error, StatusCodes.Status404NotFound, logger); public static ProblemHttpResult ToProblemDetails(this NotDeleted error, ILogger logger) => ToProblemDetails(error, StatusCodes.Status500InternalServerError, logger); }
- 在端点方法中注入
ILogger并传入:
private static async Task<Results<ProblemHttpResult, Ok<MovieResponse>>> DeleteMovieAsync(int id, MovieService ms, ILogger<MovieEndpoints> logger, CancellationToken ct) { var result = await ms.DeleteMovieAsync(id, ct); return result.Match<Results<ProblemHttpResult, Ok<MovieResponse>>>( notFoundError => notFoundError.ToProblemDetails(logger), notDeletedError => notDeletedError.ToProblemDetails(logger), movie => TypedResults.Ok(movie.MapToMovieResponse())); }
方案二:将ErrorMapper改为可注入的非静态类
如果不想每次调用都传递ILogger,可以重构ErrorMapper为依赖注入类:
- 修改
ErrorMapper为非静态类,注入ILogger:
// Mappers public class ErrorMapper { private readonly ILogger<ErrorMapper> _logger; public ErrorMapper(ILogger<ErrorMapper> logger) { _logger = logger; } public ProblemHttpResult ToProblemDetails(Error error, int statusCode) { if (statusCode == StatusCodes.Status404NotFound) { _logger.LogWarning("Error: {Code} - {Description}", error.Code, error.Description); } else { _logger.LogError("Error: {Code} - {Description}", error.Code, error.Description); } return TypedResults.Problem(new ProblemDetails { Title = error.Code, Detail = error.Description, Status = statusCode }); } public ProblemHttpResult ToProblemDetails(NotFound error) => ToProblemDetails(error, StatusCodes.Status404NotFound); public ProblemHttpResult ToProblemDetails(NotDeleted error) => ToProblemDetails(error, StatusCodes.Status500InternalServerError); }
- 在Program.cs中注册服务:
builder.Services.AddScoped<ErrorMapper>();
- 在端点方法中注入
ErrorMapper并使用:
private static async Task<Results<ProblemHttpResult, Ok<MovieResponse>>> DeleteMovieAsync(int id, MovieService ms, ErrorMapper errorMapper, CancellationToken ct) { var result = await ms.DeleteMovieAsync(id, ct); return result.Match<Results<ProblemHttpResult, Ok<MovieResponse>>>( notFoundError => errorMapper.ToProblemDetails(notFoundError), notDeletedError => errorMapper.ToProblemDetails(notDeletedError), movie => TypedResults.Ok(movie.MapToMovieResponse())); }
方案三:通过ServiceProvider获取ILogger(不推荐)
此方案会破坏依赖注入原则,增加代码耦合,仅作为临时备选:
// 在Program.cs中保存ServiceProvider引用 public static class ServiceProviderHolder { public static IServiceProvider? ServiceProvider { get; set; } } // Program.cs中初始化 var app = builder.Build(); ServiceProviderHolder.ServiceProvider = app.Services; // 修改ErrorMapper的ToProblemDetails方法 private static ProblemHttpResult ToProblemDetails(Error error, int statusCode) { if (ServiceProviderHolder.ServiceProvider is null) throw new InvalidOperationException("ServiceProvider未初始化"); var logger = ServiceProviderHolder.ServiceProvider.GetRequiredService<ILogger<ErrorMapper>>(); if (statusCode == StatusCodes.Status404NotFound) { logger.LogWarning("Error: {Code} - {Description}", error.Code, error.Description); } else { logger.LogError("Error: {Code} - {Description}", error.Code, error.Description); } return TypedResults.Problem(new ProblemDetails { Title = error.Code, Detail = error.Description, Status = statusCode }); }
内容的提问来源于stack exchange,提问作者D G
相关产品推荐
相关产品推荐

