1

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

3 Answers 3

3

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

  1. Do not catch an exception of type 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.
  2. Your API is asking the client to provide you with a string for a category. So long as they provide it, even if it is "xaoudis garbage", they have done what they are supposed to do: Provide you with a string. Now it is your responsibility to do your best and provide them with a result. The result can be a list of items in that category or an error.

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();
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

This is solid. It test every level. I will follow this. Thanks
1

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.

Comments

0

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.

4 Comments

Hang on - this looks like MVC not WebAPI, should Startup.ConfigureServices be WebApiConfig.Register?
I think that depends on the version of ASP.Net respectively ASP.Net Core. In this tutorial here, learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api, it's definitely a Web API project but uses the StartUp class and services.UseMvc(). Again, I don't claim to be an expert in this. Maybe user2320476 can specify, which framework and version he/she is using.
This is webapi2 and not .net core
Anybody objects if I delete my answer? It obviously does not apply to the question asked.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.