如何消除控制器方法中重复的null检查和异常处理?

时间:2019-04-05 13:54:21

标签: c# design-patterns .net-core asp.net-core-webapi

我想避免使用重复的try / catch块,重复的日志记录命令,并考虑HTTP响应代码(例如404、200、204等),并将API方法的代码行最小化到某些接口或服务中。换句话说,使我的代码更干燥。

给出以下代码:

    [HttpGet()]
    [Route("Contracts/{id}")]
    public async Task<IActionResult> Get(int id)
    {
        try
        { 
            var results = await _service.GetContractByIdAsync(id);
            if (results == null) { return NotFound(); }             
            return Ok(results);
        }
        catch(Exception ex)
        {
            _log(ex);
            return StatusCode(500); 
        }
    }

如果我有多种类似的方法,则除了以下每一行代码

var results = await _service.GetContractByIdAsync(id);

将重复。如何避免重复?也许以后我想改变我处理错误的方式,并且我不想在很多地方都改变它。

2 个答案:

答案 0 :(得分:1)

您可以定义一个ResultFilterAttribute,它将所有具有空值的ObjectResult更改为NotFoundResult。您可以在任何适用的地方注释控制器方法,甚至可以全局注册过滤器。另请参阅文章Convert Null Valued Results to 404 in asp.net-core mvc,了解如何以及为什么。

属性看起来像这样(所述文章的代码)

public class NotFoundResultFilterAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is ObjectResult objectResult && objectResult.Value == null)
        {
            context.Result = new NotFoundResult();
        }
    }
}

将其应用于您的控制器方法,然后返回结果。

[HttpGet()]
[Route("Contracts/{id}")]
[NotFoundResult]
public async Task<IActionResult> Get(int id) {
    var results = await _service.GetContractByIdAsync(id);
    return results;
}

确保您的控制器装饰有ApiController属性。我省略了try-catch-block,因为您还可以让管道为您处理异常。在您的Startup.Configure方法中,为app.UseExceptionHandler(...)注册一个lambda。

有关详细信息,请查看Microsoft ASP.NET Core文档-section error handling

答案 1 :(得分:1)

挑战1:尝试/抓住

您可以在MVC之前添加middleware,这将在try / catch块中执行下一个中间件(包括MVC):

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //...

    app.UseMyExceptionHandler();
    // ...
    app.UseMvc();
}

中间件可能看起来像这样:

public class ExceptionHandlerMiddleware
{
    readonly RequestDelegate _next;
    readonly ILogger logger;

    public ExceptionHandlerMiddleware(RequestDelegate next, ILogger<ExceptionHandlerMiddleware> logger)
    {
        _next = next;
        this.logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception e)
        {
            await HandleExceptionAsync(context, e);
        }
    }

    private Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        // Log the exceptions
        string result = ... cretate the response if you need it
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = 500;
        return context.Response.WriteAsync(result);
    }
}

然后我使用扩展方法将其添加到应用构建器中:

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseMyExceptionHandler(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ExceptionHandlerMiddleware>();
    }
}

现在,您可以从所有控制器操作中删除try/catch,因为所有未处理的异常都将被中间件捕获。

还有一些built-in ways可以帮助您的事情。

挑战2:对NotFound进行空检查

您可以创建一些所有控制器都将继承的基本控制器,并处理其中的null检查:

public class BaseController : ControllerBase
{
    protected IActionResult CreateResponse(object result)
    {
        if (results == null)
            return NotFound();
        return Ok(results);
    }
}

,然后使所有控制器都继承自它:

[ApiController]
public class YourController : BaseController
// ...

现在您的操作方法可能如下所示:

[HttpGet()]
[Route("Contracts/{id}")]
public async Task<IActionResult> Get(int id)
{
    var results = await _service.GetContractByIdAsync(id);
    return CreateResponse(results);
}