【问题标题】:Error handling (Sending ex.Message to the client)错误处理(向客户端发送 ex.Message)
【发布时间】:2016-10-27 03:09:01
【问题描述】:

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

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

我不明白为什么这不是人们需要的常见事物。如果真的没有解决办法,谁能告诉我为什么?

我确实看到了一些使用 HttpResponseException(HttpResponseMessage) 的文档,但为了使用它,我必须安装兼容 shim。在 Core 1.0 中是否有一种新的方式来处理这些事情?

这是我一直在尝试使用 shim 的东西,但它不起作用:

// 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 被抛出时,我查看客户端并在内容中找不到我正在发送任何内容的消息。

【问题讨论】:

  • 在 ASP.Net 中,我们通常会记录错误,而不是向用户显示,而是向用户显示发生错误的 JavaScript 警报。
  • @AshrafAbusada 真的是这样吗?您如何在 JavaScript 中为用户提供详细的警报。假设我的 Web API 的业务逻辑非常复杂,它可能会引发几个不同的异常,例如:“客户未正确设置”和“产品无效”,当引发此异常时,确实无法传递它在响应的标头或内容中发送给客户端?
  • @BlakeRivell 我们将错误包装到自己的自定义错误代码中,或者如果需要更详细的消息,则将其包装到自定义错误 DTO 对象中,并将其发送给客户端(同时更改响应状态代码以显示 HTTP代码错误,如 500)。然后客户端分析,HTTP 状态代码不是 200 OK,并根据自定义错误代码显示预定义的警报消息(+ 来自自定义错误 DTO 的数据)
  • 您可以告诉您的客户您喜欢什么,状态码只是指示您的客户解释响应的方式。另外,它们不是自定义错误代码,它们称为 HTTP 状态代码。
  • HTTP 状态码是一个标准,并且围绕它们有很好的书面文档。任何值得他们重视的客户端开发人员都应该了解其中的大多数。但是,您发送给客户端的响应将有一个描述结果的对象 - 因此知道如何处理它应该是微不足道的。

标签: c# asp.net asp.net-web-api asp.net-core asp.net-core-1.0


【解决方案1】:

您可以创建一个自定义异常过滤器,如下所示

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" };
    }
}

【讨论】:

  • 我还会添加基于类型的异常处理,这样您就可以确定返回的状态码
  • @CallumLinington 这看起来是一个不错的解决方案,但状态码仍然返回正常。有没有办法改变它?此外,我在哪里查看消息“Suckers”的响应?我在任何地方都找不到它。
  • 您也可以通过执行 services.AddMvc(config => { services.Filters.Add(typeof(CustomExceptionFilterAttribute)); } 将过滤器添加到 Startup.cs 中的 ConfigureServices(IServiceCollection services) 中的所有控制器);
  • @Blake Rivell:看看 shabbirh 的解决方案。那个显示了如何修改 HTTP 状态码。
【解决方案2】:

这是一个简单的错误 DTO 类

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);
                    }
                });
            });

【讨论】:

  • 这是一个很好的解决方案,我相信它遵循新的 asp.net 核心文档:docs.asp.net/en/latest/fundamentals/error-handling.html# 我会试一试。
  • 我应该把它放在app.UseMVC之前还是之后?
  • 放在前面。 app.UseMVC() 在大多数情况下应该是最后一个。
  • 我只是简单地在我的控制器中的某个地方抛出一个异常来测试?
  • 是的,够了
【解决方案3】:

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

在您的 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 状态代码。

希望对您有所帮助。

【讨论】:

  • 添加“;”在“ContentResult”的和处,并将“test/plain”替换为“text/plain”或更好地替换为“System.Net.Mime.MediaTypeNames.Text.Plain”。
  • 我刚刚更正了 shabbirh 的类型。 @Skorunka František,您在哪个 Nuget 包中找到 System.Net.Mime.MediaTypeNames.Text.Plain?我正在使用 ASP.Net Core 1.1.1。
  • @Christoph:我相信,它是 system.dll 的一部分 msdn.microsoft.com/en-us/library/…
【解决方案4】:

也许这会有所帮助。您可以只返回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);
}

【讨论】:

    【解决方案5】:

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

    // 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 对象而不是文本。

    【讨论】:

    • 这是一个自定义解决方案,因为它不适用于像 _customersService.GetCustomerById(id); 这样的代码可能在内部引发异常的情况。
    • 真的。它只是原始帖子中代码的一个更简单的工作版本。
    • OP 声明“我的控制器正在调用错误”。没有提到服务抛出异常。只是想表明,在示例代码中,简单的 404 不需要异常。
    • 这总是假设 db 调用是在 Controller 中完成的,你如何从内部的服务(而不是来自控制器)、不同的项目、类返回 NotFound()?
    • 这可能是服务器端错误。您应该抓住它并返回 500。类也可以返回自定义错误,因为这些错误应该在内部处理,并且控制器可以相应地处理该结果。
    【解决方案6】:

    我遇到了同样的问题,经过一番研究,我发现我可以使用 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 以 JSON 格式返回错误消息。如果调用成功,我也会将响应打包成 JSON。

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

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

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

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

    【讨论】:

    【解决方案7】:

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

    使用最少以下属性定义错误响应类

    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);
                }
            }
    

    接下来在startup.cs类中使用这个类作为中间件

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-08
      • 2012-08-17
      • 2019-01-26
      • 2018-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-14
      相关资源
      最近更新 更多