错误处理(Sending ex.Message to client)

Error handling (Sending ex.Message to the client)

我有一个 ASP.NET Core 1.0 Web API 应用程序,并试图找出如果我的控制器调用的函数出错时如何将异常消息传递给客户端。

我尝试了很多东西,但没有一个实现 IActionResult

我不明白为什么这不是人们需要的普通东西。如果真的没有解决办法谁能告诉我为什么?

我确实看到了一些使用 HttpResponseException(HttpResponseMessage) 的文档,但为了使用它,我必须安装 compat shim。在 Core 1.0 中有做这些事情的新方法吗?

这是我一直在尝试使用垫片但它不起作用的方法:

// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
    Customer c = _customersService.GetCustomerById(id);
    if (c == null)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            Content = new StringContent("Customer doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
            StatusCode = HttpStatusCode.NotFound

        };

        throw new HttpResponseException(response);

        //return NotFound();
    }
    return new ObjectResult(c);
}

当抛出 HttpResponseException 时,我查看客户端,在内容中找不到我正在发送的消息。

您可以像下面这样创建自定义异常过滤器

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.Result = new JsonResult(exception.Message);
    }
}

然后将上述属性应用到您的控制器。

[Route("api/[controller]")]
[CustomExceptionFilter]
public class ValuesController : Controller
{
     // GET: api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        throw new Exception("Suckers");
        return new string[] { "value1", "value2" };
    }
}

这是一个简单的错误DTO class

public class ErrorDto
{
    public int Code {get;set;}
    public string Message { get; set; }

    // other fields

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

然后使用ExceptionHandler中间件:

            app.UseExceptionHandler(errorApp =>
            {
                errorApp.Run(async context =>
                {
                    context.Response.StatusCode = 500; // or another Status accordingly to Exception Type
                    context.Response.ContentType = "application/json";

                    var error = context.Features.Get<IExceptionHandlerFeature>();
                    if (error != null)
                    {
                        var ex = error.Error;

                        await context.Response.WriteAsync(new ErrorDto()
                        {
                            Code = <your custom code based on Exception Type>,
                            Message = ex.Message // or your custom message
                            // other custom data
                        }.ToString(), Encoding.UTF8);
                    }
                });
            });

是的,可以将状态代码更改为您需要的任何内容:

在你的CustomExceptionFilterAttribute.cs文件中修改代码如下:

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        context.Result = new ContentResult
        {
            Content = $"Error: {exception.Message}",
            ContentType = "text/plain",
            // change to whatever status code you want to send out
            StatusCode = (int?)HttpStatusCode.BadRequest 
        };
    }
}

差不多就这些了。

如果您有自定义异常,那么您也可以在从上下文中抓取抛出的异常时检查它们。之后,您可以根据代码中发生的情况发送不同的 HTTP 状态代码。

希望对您有所帮助。

也许这有帮助。您可以 return 只是 object 并发送例如 BadRequest (HTTP 代码:400),您的自定义 object 作为实际参数(我只是在这里使用了一个内插字符串)但是你可以放任何东西。

在您的客户端,您可以使用 AJAX error handler.

捕获该错误情况
// GET: api/TruckFahrerGeoData
[HttpGet]
public object GetTruckFahrerGeoData()
{

    var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();

    var geodataItems = _context.TruckFahrerGeoData;

    foreach (var truckFahrerGeoData in geodataItems)
    {
        GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);

        if (geoTelemetryData == null)
        {
            return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
        }
        TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
        {
            Speed = geoTelemetryData.Speed,
            Accuracy = geoTelemetryData.Accuracy,
            TruckAppId = geoTelemetryData.Activity.TruckAppId,
            TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
            ClId = geoTelemetryData.Activity.ClId,
            TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
            TaskId = geoTelemetryData.Activity.TaskId,
            TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
        };

        truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
    }


    return truckFahrerGeoDataItems;
}

或者像这样使用 IActionResult 的更简洁的方法:

// GET: api/TruckFahrerGeoData
[HttpGet]
public IActionResult GetTruckFahrerGeoData()
{

    var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();

    var geodataItems = _context.TruckFahrerGeoData;

    foreach (var truckFahrerGeoData in geodataItems)
    {
        GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);

        if (geoTelemetryData == null)
        {
            return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
        }
        TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
        {
            Speed = geoTelemetryData.Speed,
            Accuracy = geoTelemetryData.Accuracy,
            TruckAppId = geoTelemetryData.Activity.TruckAppId,
            TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
            ClId = geoTelemetryData.Activity.ClId,
            TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
            TaskId = geoTelemetryData.Activity.TaskId,
            TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
        };

        truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
    }


    return Ok(truckFahrerGeoDataItems);
}

与其引发并捕获异常,不如将操作简化为:

// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
    var customer = _customersService.GetCustomerById(id);

    if (customer == null)
    {
        return NotFound("Customer doesn't exist");        
    }

    return Ok(customer);
}

我写了一个 blog post 有更多选项,例如返回一个 JSON 对象而不是文本。

我遇到了同样的问题,经过一些研究,我发现我可以使用 HttpClient 调用我的 API 并轻松读取响应。当 HTTP 响应包含错误代码时,HttpClient 不会引发任何错误,但会将 IsSuccessStatusCode 属性 设置为 false。

这是我使用 HttpClient 的函数。我从我的控制器调用它。

  public static async Task<HttpResponseMessage> HttpClientPost(string header, string postdata, string url)
        {
            string uri = apiUrl + url;
            using (var client = new HttpClient())
            {
                //client.BaseAddress = new Uri(uri);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", header);
                HttpResponseMessage response = await client.PostAsync(uri, new StringContent(postdata));

                return response;
            }
        }

这是我的控制器代码,我在其中调用函数并读取响应并确定我是否有错误并做出相应响应。请注意,我正在检查 IsSuccessStatusCode。

                HttpResponseMessage response;
                string url = $"Setup/AddDonor";
                var postdata = JsonConvert.SerializeObject(donor);

                response = await ApiHandler.HttpClientPost(HttpContext.Session.GetString(tokenName), postdata, url);
                //var headers = response.Headers.Concat(response.Content.Headers);
                var responseBody = await response.Content.ReadAsStringAsync();

                if (response.IsSuccessStatusCode)
                {
                    tnxresult = JsonConvert.DeserializeObject<TnxResult>(AppFunctions.CleanResponse(responseBody));

                    return Json(new
                    {
                        ok = true,
                        message = tnxresult.Message,
                        statusCode = tnxresult.StatusCode
                    });
                }
                else
                {
                  ApiError rs = JsonConvert.DeserializeObject<ApiError>(AppFunctions.CleanResponse(responseBody));

                    return Json(new
                    {
                        ok = false,
                        message = rs.Message,
                        statusCode = rs.StatusCode
                    });

                }

我的 API returns 错误消息在 JSON 中。如果调用成功,我也在 JSON 中打包响应。

关键的代码行是这一行...

var responseBody = await response.Content.ReadAsStringAsync();

它将 HTTP 内容序列化为字符串作为异步操作。

之后,我可以将我的 JSON 字符串转换为对象并访问 error/success 消息和状态代码。

聚会晚了,但正在完善答案。

定义您的错误响应 class 至少具有以下属性

using Microsoft.AspNetCore.Http;

    public class ErrorResponse
        {
            private readonly RequestDelegate next;
            public ErrorResponse(RequestDelegate next)
            {
                this.next = next;
            }
    
            public async Task Invoke(HttpContext context )
            {
                try
                {
                    await next(context);
                }
                catch (Exception ex)
                {
                    await HandleExceptionAsync(context, ex);
                }
            }
    
            private static Task HandleExceptionAsync(HttpContext context, Exception ex)
            {
                var code = HttpStatusCode.InternalServerError;         
                string result = string.Empty;
                object data = new object();
                if (ex is ForbiddenException)
                {
                    code = HttpStatusCode.Forbidden;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.Forbidden(ex.Message), data));
                }
                else if(ex is BadRequestException){
                    code = HttpStatusCode.BadRequest;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.BadRequest(ex.Message), data));
                }
                else if (ex is NotFoundException)
                {
                    code = HttpStatusCode.NotFound;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.NotFound(ex.Message), data));
                }
                else if (ex is UnauthorizedException)
                {
                    code = HttpStatusCode.Unauthorized;
                    result = JsonConvert.SerializeObject(new Response<object>(Status.Unauthorized(ex.Message), data));
                }
                else
                {
                    result = JsonConvert.SerializeObject(new Response<object>(Status.InternalServerError(ex.Message), data));
                }
    
    
                context.Response.ContentType = "application/json";
                context.Response.StatusCode = (int)code;
                return context.Response.WriteAsync(result);
            }
        }

接下来使用这个 class 作为 startup.cs 中的中间件 class

app.UseHttpsRedirection();
app.UseMiddleware(typeof(ErrorResponse)); 

现在每个请求和响应都将经过此 class,如果发生错误,则错误代码将设置为 true 并带有错误代码。如下示例响应

data: {}
status: {
code: 404
error: true
message: "No employee data found"
type: "Not Found"
}