Reputation: 2229
I have the method below in my webapi. I want to be able to pass the exception to post man and see the error. I tried using "return BadRequest(ex.Message);" and I got errors because of the return type of the method.
How can I correct this such that I can return the actual error message if any?
// GET api/Articles/News
public IEnumerable<ArticlesDto> Get(string category)
{
IEnumerable<ArticlesDto> articlesByCategory = null;
try
{
if (category == null)
{
}
articlesByCategory = _articlesrepository.Find(category);
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
return articlesByCategory;
}
Upvotes: 1
Views: 10514
Reputation: 27039
There are a few issues with what you are doing. Let's go over them firstly and then we will go over a better approach.
Issues
Exception
and then tell the client their request is a bad request. If you have a DivideByZeroException
, db not found exception, or InvalidOperationException
or any other exception, you will tell the client their request is bad. This will clearly not be true.Returning a Response from Web API
Returning a domain object (or a DTO) is fine but if you want to have a finer level of control over the response then use HttpResponseMessage
. Here is an examplef (please read the comments in code for more information):
public HttpResponseMessage Get(string category)
{
// Step 1: First check the obvious issues
if (string.IsNullOrWhiteSpace(category))
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
try
{
// The client has sent us a category. Now we have to do our best to
// satisfy the request.
// Step 2: Optional Step: First check to see if we have the category
string cat = _categoryRepository.Get(category);
if (string.IsNullOrWhiteSpace(cat))
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent($"The category with the name {category} was not found.");
throw new HttpResponseException(message);
}
// Step 3: Category exists so let's return the products
IEnumerable<ArticlesDto> articlesByCategory = _articlesrepository.Find(category);
// Even if the list is empty, we can still return it to tell
// the client 0 items were found
// for the category.
return Request.CreateResponse(HttpStatusCode.OK, articlesByCategory);
}
catch (Exception ex)
{
// Something went wrong on our side (NOT the client's fault). So we need to:
// 1. Log the error so we can troubleshoot it later
// 2. Let the client know it is not their fault but our fault.
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
Web API 2
With Web API 2, you can do it like this which is much easier and cleaner. Please change the code as per your requirements.
public IHttpActionResult Get(string category)
{
try
{
// code...
return Ok(articlesByCategory);
}
catch (Exception ex)
{
// Something went wrong on our side (NOT the client's fault). So we need to:
// 1. Log the error so we can troubleshoot it later
// 2. Let the client know it is not their fault but our fault.
return InternalServerError();
}
}
Upvotes: 3
Reputation: 852
For you case, I think throw out the HttpResponseException
with a HttpResponseMessage
contains the exception message would work. Flowing code snippet has been tested my end.
public IEnumerable<string> Get()
{
try
{
throw new InvalidOperationException("Invalid Operation");
}
catch(Exception ex)
{
var res = new HttpResponseMessage(HttpStatusCode.InternalServerError);
res.Content = new StringContent(ex.Message);
throw new HttpResponseException(res);
}
}
For more information about how to handle exception in WebAPI flow, refer to this official guide. Hope it is helpful for you.
Upvotes: 1
Reputation: 2317
There are probably some other ways to do this (I don't claim to be an ASP.Net Core expert) but I have solved this problem the following way. First, define a custom exception class. The purpose is that you can actually throw this without regard to any controller method return type. Also, throwing exceptions makes control flow a lot more structured.
public class CustomApiException : Exception
{
/// <summary>
/// Optional application-specific error code returned to the client.
/// </summary>
public int? ApplicationErrorCode { get; private set; } = null;
/// <summary>
/// HTTP status code returned to the client.
/// </summary>
public HttpStatusCode HttpStatusCode { get; private set; } = HttpStatusCode.BadRequest;
public CustomApiException() : base() { }
public CustomApiException(string message) : base(message) { }
public CustomApiException(string message, HttpStatusCode httpStatusCode) : base(message)
{
HttpStatusCode = httpStatusCode;
}
public CustomApiException(string message, HttpStatusCode httpStatusCode, int? applicationErrorCode) : base(message)
{
HttpStatusCode = httpStatusCode;
ApplicationErrorCode = applicationErrorCode;
}
public CustomApiException(string message, int? applicationErrorCode) : base(message)
{
ApplicationErrorCode = applicationErrorCode;
}
}
Then define a custom ExceptionFilterAttribute. Please note that this copy/pasted snippet does a bit more than what you have been asking for. E.g. depending on the development vs. production it will include the entire stack trace of the exception (of any exception actually, not just CustomApiException).
// todo: turn into async filter.
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ILogger<ApiExceptionFilterAttribute> _logger;
private readonly IHostingEnvironment _env;
public ApiExceptionFilterAttribute(ILogger<ApiExceptionFilterAttribute> logger, IHostingEnvironment env)
{
_logger = logger;
_env = env;
}
public override void OnException(ExceptionContext context)
{
_logger.LogError(new EventId(0), context.Exception, context.Exception.Message);
dynamic errObject = new JObject();
HttpStatusCode statusCode = HttpStatusCode.InternalServerError; // use 500 unless the API says it's a client error
if (context.Exception.GetType() == typeof(CustomApiException))
{
CustomApiException customEx = (CustomApiException)context.Exception;
if (customEx.ApplicationErrorCode != null) errObject.errorCode = customEx.ApplicationErrorCode;
errObject.errorMessage = customEx.Message;
statusCode = customEx.HttpStatusCode;
}
if (_env.IsDevelopment())
{
errObject.errorMessage = context.Exception.Message;
errObject.type = context.Exception.GetType().ToString();
errObject.stackTrace = context.Exception.StackTrace;
}
JsonResult result = new JsonResult(errObject);
result.StatusCode = (int?)statusCode;
context.Result = result;
}
}
Finally, add the custom ExceptionFilterAttribute to the global ConfigureServices method.
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//...
// Add framework services.
services.AddMvc(options =>
{
options.Filters.Add(typeof(ApiExceptionFilterAttribute));
});
}
// ...
}
It's a little bit of work but only one-off work, and pretty powerful once you have added it. If I remember correctly, my solution is based on this MS page Exception Handling. This may be of help if you have further questions.
Upvotes: 0