如何使用Web API控制器中的分层体系结构处理服务器错误

Kon*_*rad 6 c# domain-driven-design entity-framework-core asp.net-core asp.net-core-webapi

编辑:

我只是意识到SaveChangesAsync返回0并不意味着它失败,实体框架在发生某些事情失败时总是会抛出异常,因此检查SaveChanges == 0是否多余!在下面的示例中,保存更改应始终返回1,如果失败,则将引发异常。

但是,在某些情况下,如果使用其他东西而不是实体框架,那么这个问题也适用。


服务器可能会失败,将所有数据访问代码放入控制器时,我可以通过以下方式处理:

[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
    await _dbContext.AddAsync(item);
    if (await _dbContext.SaveChangesAsync() == 0)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}
Run Code Online (Sandbox Code Playgroud)

当我的数据访问封装在服务层中时,应该如何处理?

public class ItemsService
{
    public async Task<Item> CreateAsync(Item item)
    {
        await _dbContext.AddAsync(item);
        if (await _dbContext.SaveChangesAsync() == 0)
        {
            return null;
        }
        return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,它将像这样使用:

[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
   // model state validation skipped for the sake of simplicity,
   // that would return BadRequest or some more valuable information
    var item = await _itemsService.CreateAsync(item);
    if (item == null)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}
Run Code Online (Sandbox Code Playgroud)

也许这可以很好地进行创建,因为只有2个状态代码,但让我们考虑在可能存在2个以上错误的地方进行更新:

  • 找不到(404)
  • 服务器内部错误(500)
  • 还行(200)

没有服务的代码:

[HttpPut("{id}")]
public async Task<ActionResult<Item>> UpdateAsync(int id, [FromBody] Item itemToUpdate)
{
    var item = await _dbContext.Items.FindAsync(id);
    if (item == null)
    {
        return NotFound();
    }

    // update item with itemToUpdate
    //...
    await _dbContext.Update(item);
    if (await _dbContext.SaveChangesAsync() == 0)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return item;
}
Run Code Online (Sandbox Code Playgroud)

现在使用服务无法正确处理:

public class ItemsService
{
    public async Task<Item> UpdateAsync(Item updateItem)
    {
        var item = await _dbContext.Items.FindAsync(id);
        if (item == null)
        {
            return null;
        }
        //change some properties and update
        //...
        _dbContext.Items.Update(item);
        if (await _dbContext.SaveChangesAsync() == 0)
        {
            // what now?
        }
        return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

因为它总是返回null,所以无法判断是否未找到该项目或保存失败。

如何正确处理?

注意:我没有添加DTO或类似的东西来简化这个例子。

Cod*_*und 2

带有服务的代码要好得多。控制器应将业务逻辑委托给服务层。

为了让你的服​​务层实现得更好,你应该:

  • 当您的服务发生错误时抛出自定义异常。示例:抛出ItemNotFoundExcption而不是返回nullItemUpdateException当更新失败时。
  • 在您的控制器中,您需要捕获可能发生的所有可能的自定义异常,并执行需要执行的操作,例如返回NotFound()forItemNotFoundExceptionBadRequest()for ItemUpdateException
  • 后一点可以通过自定义操作过滤器来实现,这可以帮助您使控制器的操作更加精简。