【问题标题】:How to handle unsuccessful requests in service layer?如何处理服务层不成功的请求?
【发布时间】:2020-08-17 18:14:34
【问题描述】:

假设我有以下使用服务层的 rest api 端点:

[HttpGet("id")]
public async Task<IActionResult> GetById(int id)
{
    var entity = _someService.GetById(id);
    
    return Ok(entity);
}

在服务方法中,用户没有权限访问该实体:

public Entity GetById(int id)
{
    var entity = _repository.Get(id)
    
    if(entity.OwnerId != CurrentUserId) // true
    {
        ...
    }
}

或者它根本不存在:

public Entity GetById(int id)
{
    var entity = _repository.Get(id)
    
    if(entity is null) // true
    {
        ...
    }
}

处理此类情况并通过在响应中设置合适的状态代码让用户知道出现问题的最佳方法是什么?

我能想到的是定义结果类,其中还包含请求结果,例如:

public class RequestResult<T>
{
    public T Result {get; set;}
    public int StatusCode {get; set;} // 200, 404, 403 ...
}

或者(我个人更喜欢)定义并抛出 ForbiddenException 或 NotFoundException 并通过设置合适的响应状态码在 ExceptionHandlingMiddleware 中处理这些。

【问题讨论】:

    标签: c# asp.net-core architecture


    【解决方案1】:

    您的服务层不是定义 HTTP 响应的正确位置;这是您的控制器的责任。

    ControllerBase 定义了许多方法来生成各种 HTTP 响应,包括您的示例中的 Ok,它返回 200。

    还有ForbidNotFound,应该可以满足你的要求。

    例如,如果找不到实体,则让您的服务返回 null:

    [HttpGet("id")]
    public async Task<IActionResult> GetById(int id)
    {
        var entity = _someService.GetById(id);
    
        if (entity == null) return NotFound();
        
        return Ok(entity);
    }
    

    如果服务确定用户无权访问,则可能会出现异常:

    public Entity GetById(int id)
    {
        var entity = _repository.Get(id)
        
        if (entity.OwnerId != CurrentUserId) // true
        {
            throw new UnauthorizedAccessException();
        }
    }
    

    然后在你的控制器中:

    [HttpGet("id")]
    public async Task<IActionResult> GetById(int id)
    {
        Entity entity;
        try
        {
            entity = _someService.GetById(id);
        }
        catch (UnauthorizedAccessException)
        {
            return Forbid();
        }
    
        if (entity == null) return NotFound();
        
        return Ok(entity);
    }
    

    【讨论】:

    • 你觉得比使用中间件处理异常更好吗?毕竟,我们可以处理其他异常类型,而且这个解决方案拓宽了我们控制器的代码。
    • @del 减少控制器代码中的行数没有任何意义。上面GetById中的所有内容都与HTTP请求的处理有关,这是一个控制器方法的目的。当然,如果异常处理逻辑需要可重用,那么中间件是实现这一目标的一种方法。关键信息是您的服务层不应关注特定于 UI 的概念,例如 HTTP 响应,就像您的控制器不应包含业务逻辑一样。
    【解决方案2】:

    处理此类情况并返回 403 或 404 的最佳方法是什么?

    根据 hTTO 定义 404 - 未找到。

    403 是被禁止的——这在一般安全性和 READ 操作方面毫无意义。基本上:如果您不允许阅读它,通常也不允许您知道它是否存在。这是现在的标准 - 我不会告诉你密码错误,我会告诉你用户名或密码错误。我从不提供安全相关信息。

    因此,403 被隔离以更新操作,您可以阅读,但禁止更新(或删除)。出于安全问题,不允许读取变成 404(未找到)。

    【讨论】:

    • 我知道在这种情况下应该返回 403,但这只是一个简单的例子。我要问的是如何在响应中设置正确的状态代码,而不是状态代码应该是什么。
    • 这根本不是你问的。通常会抛出一个设置状态代码的异常。可能将其包装在处理程序中的 Json 错误对象中。
    • 当然,这是我要问的:))谢谢你的回答,我可能会使用处理程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 2016-04-13
    • 2010-09-10
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多