J'ai une application API Web ASP.NET Core 1.0 et j'essaie de comprendre comment transmettre le message d'exception au client si une fonction que mon contrôleur appelle en erreur est renvoyée.
J'ai essayé beaucoup de choses, mais rien ne met en œuvre IActionResult
.
Je ne comprends pas pourquoi ce n'est pas une chose courante dont les gens ont besoin. S'il n'y a vraiment aucune solution, quelqu'un peut-il me dire pourquoi?
Je vois de la documentation utilisant HttpResponseException(HttpResponseMessage)
, mais pour l'utiliser, je dois installer le compat shim. Existe-t-il une nouvelle façon de faire ces choses dans Core 1.0?
Voici quelque chose que j'ai essayé avec la cale mais ça ne marche pas:
// 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);
}
Lorsque le HttpResponseException
est lancé, je regarde le client et ne trouve pas le message que j'envoie dans le contenu.
Voici une simple erreur de classe DTO
public class ErrorDto
{
public int Code {get;set;}
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Et ensuite, en utilisant le middleware 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);
}
});
});
Oui, il est possible de changer le code d'état en fonction de vos besoins:
Dans votre fichier CustomExceptionFilterAttribute.cs, modifiez le code comme suit:
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
};
}
}
C'est à peu près tout.
Si vous avez des exceptions personnalisées, vous pouvez également les vérifier lorsque vous saisissez l'exception levée dans le contexte. Ensuite, vous pouvez envoyer différents codes d’état HTTP en fonction de ce qui s’est passé dans votre code.
J'espère que ça t'as aidé.
Vous pouvez créer un filtre d'exception personnalisé comme ci-dessous
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.Result = new JsonResult(exception.Message);
}
}
Appliquez ensuite l'attribut ci-dessus à votre contrôleur.
[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" };
}
}
Plutôt que de lever et de capturer une exception, que diriez-vous de simplifier votre action pour:
// 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);
}
J'ai écrit un article de blog avec quelques options supplémentaires, telles que le retour d'un objet JSON au lieu d'un texte.
Peut-être que c'est utile. Vous pouvez simplement renvoyer object
et envoyer par exemple un BadRequest
(code HTTP: 400) avec votre paramètre personnalisé object
en tant que paramètre réel (je viens d'utiliser une chaîne interpolée ici), mais vous peut mettre n'importe quoi.
Dans votre côté client, vous pouvez intercepter cette situation d'erreur, par exemple avec un gestionnaire d'erreurs 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;
}
Ou encore plus propre avec IActionResult
comme ceci:
// 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);
}
J'ai eu le même problème et après quelques recherches, j'ai découvert que je pouvais utiliser HttpClient pour appeler mon API et lire la réponse facilement. HttpClient ne génère aucune erreur lorsque la réponse HTTP contient un code d'erreur, mais définit la propriété IsSuccessStatusCode sur false.
Ceci est ma fonction en utilisant le HttpClient. J'appelle cela depuis mon contrôleur.
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;
}
}
C'est mon code de contrôleur, où j'appelle la fonction, lit la réponse et détermine si j'ai une erreur ou non et répond en conséquence. Notez que je vérifie le 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
});
}
Mon API renvoie des messages d'erreur en JSON. Si l'appel aboutit, je prépare également la réponse au format JSON.
La ligne de code cruciale est celle-ci ...
var responseBody = await response.Content.ReadAsStringAsync();
Il sérialise le contenu HTTP en chaîne sous forme d'opération asynchrone.
Après cela, je peux convertir ma chaîne JSON en un objet et accéder également au message d'erreur/succès et au code d'état.