在异常过滤器中捕获来自服务的错误

Catching errors from services in Exception Filter

我创建了一个在我的 aspnet 项目中使用的服务,该服务检索和验证 header 等。问题是异常过滤器无法捕获服务抛出的错误,因为它不在异常过滤器的范围内,因此给用户一个丑陋的内部服务器错误。有什么方法可以优雅地 return 向使用服务的用户描述参数错误?

初创公司:

services.AddScoped<UserService>();
services
    .AddMvc(x =>
    {
        x.Filters.Add(typeof(Filters.MyExceptionFilter));
    })

异常过滤器:

public class MyExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is ArgumentException argumentException)
        {
            var response = context.HttpContext.Response;
            context.ExceptionHandled = true;
            response.StatusCode = 400;
            context.Result = new ObjectResult(argumentException.Message);
            return;
        }
    }
}

服务:

public class UserService
{
    public readonly string UserId;

    public UserService(IHttpContextAccessor context)
    {
        if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
        {
            throw new ArgumentException($"x-test header is required.");
        }
        UserId = user;
        //do other stuff
    }
}

控制器操作方法:

public async Task<IActionResult> Delete(string id, 
    [FromServices] UserService userService)
{
    //do stuff
}

C# 中的一个经验法则是在构造函数中做的工作越少越好。这有几个很好的理由(例如,您不能处理在其构造函数中引发异常的 class )。另一个很好的理由是,正如您所发现的,构造可能发生在与您实际使用 class.

的地方不同的地方(例如 DI 容器)。

修复应该非常简单 - 只需将逻辑移出构造函数即可。您可以使用 Lazy<T> 来执行此操作,例如:

public class UserService
{
    public readonly Lazy<string> _userId ;

    public UserService(IHttpContextAccessor context)
    {
        _userId = new Lazy<string>(() => 
        {
            if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
            {
                throw new ArgumentException($"x-test header is required.");
            }
            
            return user;
        });

        //do other stuff
    }

    public string UserId => _userId.Value;
}

或者您可以在需要时获取值:

public class UserService
{
    public readonly IHttpContextAccessor _context;

    public UserService(IHttpContextAccessor context)
    {
        _context = context;
        //do other stuff
    }

    public string UserId
    {
        get
        {
            if (_context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
            {
                return user;
            }
            else
            {
                throw new ArgumentException($"x-test header is required.");
            }
        }
    }
}