Обработка ошибок (отправка ex.Message клиенту)
У меня есть приложение 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
, я смотрю на клиента и не могу найти сообщение, которое отправляю, в содержимом.
Ответы
Ответ 1
Вот простая ошибка класса 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);
}
});
});
Ответ 2
Да, можно изменить код состояния на все, что вам нужно:
В вашем файле 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, основываясь на том, что произошло в вашем коде.
Надеюсь, что это поможет.
Ответ 3
Вы можете создать настраиваемый фильтр исключений, как показано ниже
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" };
}
}
Ответ 4
Вместо того, чтобы поднимать и ловить исключение, как насчет упрощения вашего действия:
// 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);
}
Я написал сообщение в блоге с дополнительными параметрами, такими как возврат объекта JSON вместо текста.
Ответ 5
Может быть, это полезно. Вы можете вернуть только object
и отправили, например, BadRequest
(HTTP CODE: 400) с вашим пользовательским object
как фактическим параметром (я просто использовал интерполированную строку здесь), но вы можете вставить что угодно.
В вашей клиентской стороне вы можете поймать эту ситуацию с ошибкой, например, с помощью обработчика ошибок AJAX.
// 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);
}
Ответ 6
У меня была такая же проблема, и после некоторых исследований я обнаружил, что могу использовать HttpClient для вызова своего API и легко читать ответ. HttpClient не выдает никакой ошибки, если HTTP-ответ содержит код ошибки, но устанавливает для свойства 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 в объект и получить доступ к сообщению об ошибке/успеху и коду состояния.