Web api return error code

I have concerns on the way that we returns errors to client. Do we return error immediately by throwing HttpResponseException when we get an error: public void Post(Customer customer) { if (s...

I have concerns on the way that we returns errors to client.

Do we return error immediately by throwing HttpResponseException when we get an error:

public void Post(Customer customer)
{
    if (string.IsNullOrEmpty(customer.Name))
    {
        throw new HttpResponseException("Customer Name cannot be empty", HttpStatusCode.BadRequest) 
    }
    if (customer.Accounts.Count == 0)
    {
         throw new HttpResponseException("Customer does not have any account", HttpStatusCode.BadRequest) 
    }
}

Or we accumulate all errors then send back to client:

public void Post(Customer customer)
{
    List<string> errors = new List<string>();
    if (string.IsNullOrEmpty(customer.Name))
    {
        errors.Add("Customer Name cannot be empty"); 
    }
    if (customer.Accounts.Count == 0)
    {
         errors.Add("Customer does not have any account"); 
    }
    var responseMessage = new HttpResponseMessage<List<string>>(errors, HttpStatusCode.BadRequest);
    throw new HttpResponseException(responseMessage);
}

This is just a sample code, it does not matter either validation errors or server error, I just would like to know the best practice, the pros and cons of each approach.

Guido Leenders's user avatar

asked May 24, 2012 at 7:00

cuongle's user avatar

3

For me I usually send back an HttpResponseException and set the status code accordingly depending on the exception thrown and if the exception is fatal or not will determine whether I send back the HttpResponseException immediately.

At the end of the day it’s an API sending back responses and not views, so I think it’s fine to send back a message with the exception and status code to the consumer. I currently haven’t needed to accumulate errors and send them back as most exceptions are usually due to incorrect parameters or calls etc.

An example in my app is that sometimes the client will ask for data, but there isn’t any data available so I throw a custom NoDataAvailableException and let it bubble to the Web API app, where then in my custom filter which captures it sending back a relevant message along with the correct status code.

I am not 100% sure on what’s the best practice for this, but this is working for me currently so that’s what I’m doing.

Update:

Since I answered this question a few blog posts have been written on the topic:

https://weblogs.asp.net/fredriknormen/asp-net-web-api-exception-handling

(this one has some new features in the nightly builds)
https://learn.microsoft.com/archive/blogs/youssefm/error-handling-in-asp-net-webapi

Update 2

Update to our error handling process, we have two cases:

  1. For general errors like not found, or invalid parameters being passed to an action we return a HttpResponseException to stop processing immediately. Additionally for model errors in our actions we will hand the model state dictionary to the Request.CreateErrorResponse extension and wrap it in a HttpResponseException. Adding the model state dictionary results in a list of the model errors sent in the response body.

  2. For errors that occur in higher layers, server errors, we let the exception bubble to the Web API app, here we have a global exception filter which looks at the exception, logs it with ELMAH and tries to make sense of it setting the correct HTTP status code and a relevant friendly error message as the body again in a HttpResponseException. For exceptions that we aren’t expecting the client will receive the default 500 internal server error, but a generic message due to security reasons.

Update 3

Recently, after picking up Web API 2, for sending back general errors we now use the IHttpActionResult interface, specifically the built in classes for in the System.Web.Http.Results namespace such as NotFound, BadRequest when they fit, if they don’t we extend them, for example a NotFound result with a response message:

public class NotFoundWithMessageResult : IHttpActionResult
{
    private string message;

    public NotFoundWithMessageResult(string message)
    {
        this.message = message;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(message);
        return Task.FromResult(response);
    }
}

Callum Watkins's user avatar

answered May 24, 2012 at 9:27

gdp's user avatar

gdpgdp

7,90210 gold badges42 silver badges61 bronze badges

9

ASP.NET Web API 2 really simplified it. For example, the following code:

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        HttpError err = new HttpError(message);
        return Request.CreateResponse(HttpStatusCode.NotFound, err);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

returns the following content to the browser when the item is not found:

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Product with id = 12 not found"
}

Suggestion: Don’t throw HTTP Error 500 unless there is a catastrophic error (for example, WCF Fault Exception). Pick an appropriate HTTP status code that represents the state of your data. (See the apigee link below.)

Links:

  • Exception Handling in ASP.NET Web API (asp.net)
    and
  • RESTful API Design: what about errors? (apigee.com)

BrianS's user avatar

BrianS

12.8k15 gold badges60 silver badges122 bronze badges

answered Mar 1, 2014 at 0:10

Manish Jain's user avatar

Manish JainManish Jain

9,4895 gold badges39 silver badges44 bronze badges

12

It looks like you’re having more trouble with Validation than errors/exceptions so I’ll say a bit about both.

Validation

Controller actions should generally take Input Models where the validation is declared directly on the model.

public class Customer
{ 
    [Require]
    public string Name { get; set; }
}

Then you can use an ActionFilter that automatically sends validation messages back to the client.

public class ValidationActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var modelState = actionContext.ModelState;

        if (!modelState.IsValid) {
            actionContext.Response = actionContext.Request
                 .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
        }
    }
} 

For more information about this check out http://ben.onfabrik.com/posts/automatic-modelstate-validation-in-aspnet-mvc

Error handling

It’s best to return a message back to the client that represents the exception that happened (with relevant status code).

Out of the box you have to use Request.CreateErrorResponse(HttpStatusCode, message) if you want to specify a message. However, this ties the code to the Request object, which you shouldn’t need to do.

I usually create my own type of «safe» exception that I expect the client would know how to handle and wrap all others with a generic 500 error.

Using an action filter to handle the exceptions would look like this:

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        var exception = context.Exception as ApiException;
        if (exception != null) {
            context.Response = context.Request.CreateErrorResponse(exception.StatusCode, exception.Message);
        }
    }
}

Then you can register it globally.

GlobalConfiguration.Configuration.Filters.Add(new ApiExceptionFilterAttribute());

This is my custom exception type.

using System;
using System.Net;

namespace WebApi
{
    public class ApiException : Exception
    {
        private readonly HttpStatusCode statusCode;

        public ApiException (HttpStatusCode statusCode, string message, Exception ex)
            : base(message, ex)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode, string message)
            : base(message)
        {
            this.statusCode = statusCode;
        }

        public ApiException (HttpStatusCode statusCode)
        {
            this.statusCode = statusCode;
        }

        public HttpStatusCode StatusCode
        {
            get { return this.statusCode; }
        }
    }
}

An example exception that my API can throw.

public class NotAuthenticatedException : ApiException
{
    public NotAuthenticatedException()
        : base(HttpStatusCode.Forbidden)
    {
    }
}

Christian Casutt's user avatar

answered Mar 4, 2014 at 5:30

Daniel Little's user avatar

Daniel LittleDaniel Little

16.7k12 gold badges70 silver badges93 bronze badges

3

You can throw a HttpResponseException

HttpResponseMessage response = 
    this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "your message");
throw new HttpResponseException(response);

answered Mar 4, 2014 at 5:15

tartakynov's user avatar

tartakynovtartakynov

2,7383 gold badges25 silver badges22 bronze badges

1

If you are using ASP.NET Web API 2, the easiest way is to use the ApiController Short-Method. This will result in a BadRequestResult.

return BadRequest("message");

answered Mar 22, 2018 at 12:22

Fabian von Ellerts's user avatar

1

For Web API 2 my methods consistently return IHttpActionResult so I use…

public IHttpActionResult Save(MyEntity entity)
{
    ....
    if (...errors....)
        return ResponseMessage(
            Request.CreateResponse(
                HttpStatusCode.BadRequest, 
                validationErrors));

    // otherwise success
    return Ok(returnData);
}

answered Jan 20, 2016 at 1:53

Mick's user avatar

MickMick

6,3894 gold badges48 silver badges67 bronze badges

1

You can use custom ActionFilter in Web Api to validate model:

public class DRFValidationFilters : ActionFilterAttribute
{

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request
                .CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);

            //BadRequest(actionContext.ModelState);
        }
    }

    public override Task OnActionExecutingAsync(HttpActionContext actionContext,
        CancellationToken cancellationToken)
    {

        return Task.Factory.StartNew(() =>
        {
            if (!actionContext.ModelState.IsValid)
            {
                actionContext.Response = actionContext.Request
                    .CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        });
    }

    public class AspirantModel
    {
        public int AspirantId { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string AspirantType { get; set; }
        [RegularExpression(@"^(?([0-9]{3}))?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$",
            ErrorMessage = "Not a valid Phone number")]
        public string MobileNumber { get; set; }
        public int StateId { get; set; }
        public int CityId { get; set; }
        public int CenterId { get; set; }


        [HttpPost]
        [Route("AspirantCreate")]
        [DRFValidationFilters]
        public IHttpActionResult Create(AspirantModel aspirant)
        {
            if (aspirant != null)
            {

            }
            else
            {
                return Conflict();
            }

            return Ok();
        }
    }
}

Register CustomAttribute class in webApiConfig.cs
config.Filters.Add(new DRFValidationFilters());

Metro Smurf's user avatar

Metro Smurf

36.7k20 gold badges105 silver badges139 bronze badges

answered Mar 18, 2016 at 9:40

LokeshChikkala's user avatar

Building up upon Manish Jain‘s answer (which is meant for Web API 2 which simplifies things):

1) Use validation structures to response as many validation errors as possible. These structures can also be used to response to requests coming from forms.

public class FieldError
{
    public String FieldName { get; set; }
    public String FieldMessage { get; set; }
}

// a result will be able to inform API client about some general error/information and details information (related to invalid parameter values etc.)
public class ValidationResult<T>
{
    public bool IsError { get; set; }

    /// <summary>
    /// validation message. It is used as a success message if IsError is false, otherwise it is an error message
    /// </summary>
    public string Message { get; set; } = string.Empty;

    public List<FieldError> FieldErrors { get; set; } = new List<FieldError>();

    public T Payload { get; set; }

    public void AddFieldError(string fieldName, string fieldMessage)
    {
        if (string.IsNullOrWhiteSpace(fieldName))
            throw new ArgumentException("Empty field name");

        if (string.IsNullOrWhiteSpace(fieldMessage))
            throw new ArgumentException("Empty field message");

        // appending error to existing one, if field already contains a message
        var existingFieldError = FieldErrors.FirstOrDefault(e => e.FieldName.Equals(fieldName));
        if (existingFieldError == null)
            FieldErrors.Add(new FieldError {FieldName = fieldName, FieldMessage = fieldMessage});
        else
            existingFieldError.FieldMessage = $"{existingFieldError.FieldMessage}. {fieldMessage}";

        IsError = true;
    }

    public void AddEmptyFieldError(string fieldName, string contextInfo = null)
    {
        AddFieldError(fieldName, $"No value provided for field. Context info: {contextInfo}");
    }
}

public class ValidationResult : ValidationResult<object>
{

}

2) Service layer will return ValidationResults, regardless of operation being successful or not. E.g:

    public ValidationResult DoSomeAction(RequestFilters filters)
    {
        var ret = new ValidationResult();

        if (filters.SomeProp1 == null) ret.AddEmptyFieldError(nameof(filters.SomeProp1));
        if (filters.SomeOtherProp2 == null) ret.AddFieldError(nameof(filters.SomeOtherProp2 ), $"Failed to parse {filters.SomeOtherProp2} into integer list");

        if (filters.MinProp == null) ret.AddEmptyFieldError(nameof(filters.MinProp));
        if (filters.MaxProp == null) ret.AddEmptyFieldError(nameof(filters.MaxProp));


        // validation affecting multiple input parameters
        if (filters.MinProp > filters.MaxProp)
        {
            ret.AddFieldError(nameof(filters.MinProp, "Min prop cannot be greater than max prop"));
            ret.AddFieldError(nameof(filters.MaxProp, "Check"));
        }

        // also specify a global error message, if we have at least one error
        if (ret.IsError)
        {
            ret.Message = "Failed to perform DoSomeAction";
            return ret;
        }

        ret.Message = "Successfully performed DoSomeAction";
        return ret;
    }

3) API Controller will construct the response based on service function result

One option is to put virtually all parameters as optional and perform custom validation which return a more meaningful response. Also, I am taking care not to allow any exception to go beyond the service boundary.

    [Route("DoSomeAction")]
    [HttpPost]
    public HttpResponseMessage DoSomeAction(int? someProp1 = null, string someOtherProp2 = null, int? minProp = null, int? maxProp = null)
    {
        try
        {
            var filters = new RequestFilters 
            {
                SomeProp1 = someProp1 ,
                SomeOtherProp2 = someOtherProp2.TrySplitIntegerList() ,
                MinProp = minProp, 
                MaxProp = maxProp
            };

            var result = theService.DoSomeAction(filters);
            return !result.IsError ? Request.CreateResponse(HttpStatusCode.OK, result) : Request.CreateResponse(HttpStatusCode.BadRequest, result);
        }
        catch (Exception exc)
        {
            Logger.Log(LogLevel.Error, exc, "Failed to DoSomeAction");
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new HttpError("Failed to DoSomeAction - internal error"));
        }
    }

answered Dec 19, 2016 at 14:32

Alexei - check Codidact's user avatar

Use the built in «InternalServerError» method (available in ApiController):

return InternalServerError();
//or...
return InternalServerError(new YourException("your message"));

answered Aug 4, 2017 at 15:03

Rusty's user avatar

RustyRusty

1091 silver badge9 bronze badges

Welcome to 2022! Now we have other answers in .NET (since ASP.NET Core 2.1). Look at this article: Using the ProblemDetails Class in ASP.NET Core Web API, where the author explains the following best practices:

  1. How to implement standard IETF RFC 7807, which defines a «problem detail» as a way to carry machine-readable details of errors in an HTTP response to avoid the need to define new error response formats for HTTP APIs.
  2. How model validations use the ProblemDetails class to populate a list of validation errors — the direct answer to the question for a general rule, whether to break processing after the first error.

As a teaser, this is how the JSON output looks if we use ProductDetails and multiple errors:

enter image description here

answered Oct 5, 2022 at 11:01

Gerard Jaryczewski's user avatar

Just to update on the current state of ASP.NET WebAPI. The interface is now called IActionResult and implementation hasn’t changed much:

[JsonObject(IsReference = true)]
public class DuplicateEntityException : IActionResult
{        
    public DuplicateEntityException(object duplicateEntity, object entityId)
    {
        this.EntityType = duplicateEntity.GetType().Name;
        this.EntityId = entityId;
    }

    /// <summary>
    ///     Id of the duplicate (new) entity
    /// </summary>
    public object EntityId { get; set; }

    /// <summary>
    ///     Type of the duplicate (new) entity
    /// </summary>
    public string EntityType { get; set; }

    public Task ExecuteResultAsync(ActionContext context)
    {
        var message = new StringContent($"{this.EntityType ?? "Entity"} with id {this.EntityId ?? "(no id)"} already exist in the database");

        var response = new HttpResponseMessage(HttpStatusCode.Ambiguous) { Content = message };

        return Task.FromResult(response);
    }

    #endregion
}

answered May 10, 2016 at 7:51

Thomas Hagström's user avatar

Thomas HagströmThomas Hagström

4,1711 gold badge20 silver badges27 bronze badges

2

Try this

[HttpPost]
public async Task<ActionResult<User>> PostUser(int UserTypeId, User user)
{
  if (somethingFails)
  {
    // Return the error message like this.
    return new BadRequestObjectResult(new
    {
      message = "Something is not working here"
    });
  }

  return ok();
}

CinCout's user avatar

CinCout

9,41911 gold badges53 silver badges65 bronze badges

answered Dec 17, 2021 at 18:29

Zablon's user avatar

ZablonZablon

1171 silver badge4 bronze badges

5

Some of these answers seem to be relics of the past. I’ve found the solution below to be simple and work well. This is in .NET 6 for a Web API derived from ControllerBase.

Rather than throwing exceptions, you can directly return the various HTTP response codes as objects, along with an exact error message:

using Microsoft.AspNetCore.Mvc;

[ApiController]
public class MyWebApiController : ControllerBase
{
    [HttpPost]
    public IActionResult Process(Customer customer)
    {
        if (string.IsNullOrEmpty(customer.Name))
            return BadRequest("Customer Name cannot be empty");

        if (!Customers.Find(customer))
            return NotFound("Customer does not have any account");

        // After validating inputs, core logic goes here...

        return Ok(customer.ID);  // or simply "return Ok()" if not returning data
    }
}

See a list of error codes available here.

As for when to return the errors (OP’s question), it depends on the requirement. Returning errors as they happen means you avoid the overhead of additional processing, but then the client has to make repeated calls to get all the errors. Consider the server viewpoint also, as it may cause undesirable program behavior to continue server-side processing when an error has occurred.

answered Sep 1, 2022 at 18:56

Tawab Wakil's user avatar

Tawab WakilTawab Wakil

1,53117 silver badges31 bronze badges

For those errors where modelstate.isvalid is false, I generally send the error as it is thrown by the code. Its easy to understand for the developer who is consuming my service. I generally send the result using below code.

     if(!ModelState.IsValid) {
                List<string> errorlist=new List<string>();
                foreach (var value in ModelState.Values)
                {
                    foreach(var error in value.Errors)
                    errorlist.Add( error.Exception.ToString());
                    //errorlist.Add(value.Errors);
                }
                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest,errorlist);}

This sends the error to the client in below format which is basically a list of errors:

    [  
    "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: abc. Path 'Country',** line 6, position 16.rn   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()rn   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()rn   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)rn   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)",

       "Newtonsoft.Json.JsonReaderException: **Could not convert string to integer: ab. Path 'State'**, line 7, position 13.rn   
at Newtonsoft.Json.JsonReader.ReadAsInt32Internal()rn   
at Newtonsoft.Json.JsonTextReader.ReadAsInt32()rn   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter, Boolean inArray)rn   
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
    ]

Sylvain's user avatar

Sylvain

19k23 gold badges92 silver badges144 bronze badges

answered Jan 19, 2016 at 10:41

Ashish Sahu's user avatar

2

Request and response are the backbones of any RESTful Web API. Every status code has a specific meaning and during the development, we must make sure that we are returning a valid response with a valid status code.

What is the Status Code

Status code is a numeric value, and it is the main component of HTTP Response. The status code is issued by the server based on the operation, input data, and some other parameters.

The status code gives some useful information about the behavior of the response.

Status code categories

All status codes are divided into the following 5 categories,

  • 1xx – Informational
  • 2xx – Successful
  • 3xx – Redirection
  • 4xx – Client Error
  • 5xx – Server Error

Asp.Net Core Web API has some built-in methods to return the proper status code.

Setup the project

Let us create a new Asp.Net Core Web API application. Now in this application let’s add a new class Employee.cs under the Model folder.

  1. public class Employee  
  2. {  
  3.     public int Id { getset; }  
  4.     public string Name { getset; }  
  5.     public string Email { getset; }  
  6. }  

We also need to add a new controller with name EmployeesController at the Controllers folder.

  1. [Route(«api/[controller]»)]  
  2. [ApiController]  
  3. public class EmployeesController : ControllerBase  
  4. {  
  5.         
  6. }  

Time to create some hard-coded, in-memory data for employees. because we are only learning about the status code and how to return them from asp.net core web api so hard code data will work for the demo app. In a real application, you will get the data from a database.

For this, we will create a new private method in the EmployeesController.

  1. private List<Employee> EmployeeData()  
  2. {  
  3.     return new List<Employee>()  
  4.     {  
  5.         new Employee(){ Id=1, Name = «Employee 1», Email = «employee1@nitishkaushik.com»},  
  6.         new Employee(){ Id=2, Name = «Employee 2», Email = «employee2@nitishkaushik.com»},  
  7.         new Employee(){ Id=3, Name = «Employee 3», Email = «employee3@nitishkaushik.com»},  
  8.         new Employee(){ Id=4, Name = «Employee 4», Email = «employee4@nitishkaushik.com»},  
  9.         new Employee(){ Id=5, Name = «Employee 5», Email = «employee5@nitishkaushik.com»}  
  10.     };  
  11. }  

Let us learn about the status code and how to return them from Asp.Net Core Web API,

200 status code

This is the most common status code that is returned from Web API.

This 200 status code belongs to the Successful category. It means everything about the operation is successful.

Example

  • Get all employees data
  • Get single employee data 

Asp.Net Core has Ok() method to return 200 status code.

  1. public IActionResult GetEmployees()  
  2. {  
  3.     return Ok();  
  4. }  

 This Ok() method can have none or object parameters. 

  1. public IActionResult GetEmployees()  
  2. {  
  3.     var employees = EmployeeData();  
  4.     return Ok(employees);  
  5. }  

201 Status code

201 status code indicates that the new resource has been created successfully and the server will return the link to get that newly created resource.

 In Asp.Net Core we can use the following methods to return the 201 status code.

  • Created
  • CreatedAtAction
  • CreatedAtRoute

All these methods need the URL to get the newly created resource.

Created 

  1. [HttpPost(«»)]  
  2. public IActionResult AddEmployee([FromBody] Employee model)  
  3. {  
  4.       
  5.       
  6.     return Created(«~api/employees/1», model);  
  7. }  

CreatedAtAction

  1. [HttpGet(«{id}»)]  
  2. public IActionResult GetEmployeeById([FromRoute] int id)  
  3. {  
  4.     var employee = EmployeeData().Where(x => x.Id == id).FirstOrDefault();  
  5.     return Ok(employee);  
  6. }  
  7.   
  8. [HttpPost(«»)]  
  9. public IActionResult AddEmployee([FromBody] Employee model)  
  10. {  
  11.       
  12.     int newEmployeeId = 1;   
  13.     return CreatedAtAction(«GetEmployeeById»new { id = newEmployeeId }, model);  
  14. }   

CreatedAtRoute

  1. [HttpGet]    
  2. [Route(«{id}», Name = «getEmployeeRoute»)]    
  3. public IActionResult GetEmployeeById([FromRoute] int id)    
  4. {    
  5.     var employee = EmployeeData().Where(x => x.Id == id).FirstOrDefault();    
  6.     return Ok(employee);    
  7. }    
  8.     
  9. [HttpPost(«»)]    
  10. public IActionResult AddEmployee([FromBody] Employee model)    
  11. {    
  12.       
  13.     int newEmployeeId = 1;   
  14.     return CreatedAtRoute(«getEmployeeRoute»new { id = newEmployeeId }, model);    
  15. }    

This method will add a new response header with a key Location and a URL in the value to get this resource.

202 status code

202 status indicates that the request has been accepted but the processing is not yet complete.

In Asp.Net Core we can use the following methods to return the 202 status code.

  • Accepted
  • AcceptedAtAction
  • AcceptedAtRoute
  1. [HttpPost(«»)]  
  2. public IActionResult AddEmployee([FromBody] Employee model)  
  3. {  
  4.       
  5.     return Accepted();  
  6. }  

400 status code

400 status code indicated the bad request. It means there is something wrong in the request data.

In asp.net core we can return 400 status using the BadRequest method. 

  1. [HttpPost(«»)]  
  2. public IActionResult AddEmployee([FromBody] Employee model)  
  3. {  
  4.       
  5.     return BadRequest();    
  6. }  

404 status code

If we are looking for a resource that does not exist, then the server returns a 404 status code.

 

In asp.net core we can return 404 status using the NotFound() method.

  1. [HttpGet]  
  2. [Route(«{id}», Name = «getEmployeeRoute»)]  
  3. public IActionResult GetEmployeeById([FromRoute] int id)  
  4. {  
  5.     var employee = EmployeeData().Where(x => x.Id == id).FirstOrDefault();  
  6.     if (employee == null)  
  7.     {  
  8.         return NotFound();  
  9.     }  
  10.     return Ok(employee);  
  11. }  

301 & 302 Status code

301 & 302 are used for local redirection. It means if you are trying to access an action method and from that action method you are redirecting the call to some other action method within the same application then in the response of this request there will be a 301 or 302 status code and a new response header with name Location.

The client will send another request on the value given in the Location header.

In case you are redirecting temporary then the status code will be 302. Or if you are redirecting permanently then the status code will be 301. 

 

In asp.net core we can return 302 status using the LocalRedirect() method.

  1. [HttpGet]  
  2. [Route(«{id}», Name = «getEmployeeRoute»)]  
  3. public IActionResult GetEmployeeById([FromRoute] int id)  
  4. {  
  5.         return LocalRedirect(«~/api/employees»);  
  6. }   

In asp.net core we can return 302 status using the LocalRedirectPermanent() method.

  1. [HttpGet]  
  2. [Route(«{id}», Name = «getEmployeeRoute»)]  
  3. public IActionResult GetEmployeeById([FromRoute] int id)  
  4. {  
  5.         return LocalRedirectPermanent(«~/api/employees»);  
  6. }  

Other than this there are lots more built-in methods to return different status codes.

Click below  to learn more about Asp.Net Core Web API,

  • Asp.Net Core Web API Complete, Free and Step by Step Tutorial

Handling errors in an ASP.NET Core Web API

This post looks at the best ways to handle exceptions, validation and other invalid requests such as 404s in ASP.NET Core Web API projects and how these approaches differ from MVC error handling.

Why do we need a different approach from MVC?

In .Net Core, MVC and Web API have been combined so you now have the same controllers for both MVC actions and API actions. However, despite the similarities, when it comes to error handling, you almost certainly want to use a different approach for API errors.

MVC actions are typically executed as a result of a user action in the browser so returning an error page to the browser is the correct approach. With an API, this is not generally the case.

API calls are most often called by back-end code or javascript code and in both cases, you never want to simply display the response from the API. Instead we check the status code and parse the response to determine if our action was successful, displaying data to the user as necessary. An error page is not helpful in these situations. It bloats the response with HTML and makes client code difficult because JSON (or XML) is expected, not HTML.

While we want to return information in a different format for Web API actions, the techniques for handling errors are not so different from MVC. Much of the time, it is practically the same flow but instead of returning a View, we return JSON. Let’s look at a few examples.

The minimal approach

With MVC actions, failure to display a friendly error page is unacceptable in a professional application. With an API, while not ideal, empty response bodies are far more permissible for many invalid request types. Simply returning a 404 status code (with no response body) for an API route that does not exist may provide the client with enough information to fix their code.

With zero configuration, this is what ASP.NET Core gives us out of the box.

Depending on your requirements, this may be acceptable for many common status codes but it will rarely be sufficient for validation failures. If a client passes you invalid data, returning a 400 Bad Request is not going to be helpful enough for the client to diagnose the problem. At a minimum, we need to let them know which fields are incorrect and ideally, we would return an informative message for each failure.

With ASP.NET Web API, this is trivial. Assuming that we are using model binding, we get validation for free by using data annotations and/or IValidatableObject. Returning the validation information to the client as JSON is one easy line of code.

Here is our model:

public class GetProductRequest : IValidatableObject
{
    [Required]
    public string ProductId { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (...)
        {
            yield return new ValidationResult("ProductId is invalid", new[] { "ProductId" });
        }
    }
}

And our controller action:

[HttpGet("product")]
public IActionResult GetProduct(GetProductRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    ...
}

A missing ProductId results in a 400 status code plus a JSON response body similar to the following:

{
    "ProductId":["The ProductId field is required."]
}

This provides an absolute minimum for a client to consume our service but it is not difficult to improve upon this baseline and create a much better client experience. In the next few sections we will look at how simple it is to take our service to the next level.

Returning additional information for specific errors

If we decide that a status code only approach is too bare-bones, it is easy to provide additional information. This is highly recommended. There are many situations where a status code by itself is not enough to determine the cause of failure. If we take a 404 status code as an example, in isolation, this could mean:

  • We are making the request to the wrong site entirely (perhaps the ‘www’ site rather than the ‘api’ subdomain)
  • The domain is correct but the URL does not match a route
  • The URL correctly maps to a route but the resource does not exist

If we could provide information to distinguish between these cases, it could be very useful for a client. Here is our first attempt at dealing with the last of these:

[HttpGet("product")]
public async Task<IActionResult> GetProduct(GetProductRequest request)
{
    ...

    var model = await _db.Get(...);

    if (model == null)
    {
        return NotFound("Product not found");
    }

    return Ok(model);
}

We are now returning a more useful message but it is far from perfect. The main problem is that by using a string in the NotFound method, the framework will return this string as a plain text response rather than JSON.

As a client, a service returning a different content type for certain errors is much harder to deal with than a consistent JSON service.

This issue can quickly be rectified by changing the code to what is shown below but in the next section, we will talk about a better alternative.

return NotFound(new { message = "Product not found" });

Customising the response structure for consistency

Constructing anonymous objects on the fly is not the approach to take if you want a consistent client experience. Ideally our API should return the same response structure in all cases, even when the request was unsuccessful.

Let’s define a base ApiResponse class:

public class ApiResponse
{
    public int StatusCode { get; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string Message { get; }

    public ApiResponse(int statusCode, string message = null)
    {
        StatusCode = statusCode;
        Message = message ?? GetDefaultMessageForStatusCode(statusCode);
    }

    private static string GetDefaultMessageForStatusCode(int statusCode)
    {
        switch (statusCode)
        {
            ...
            case 404:
                return "Resource not found";
            case 500:
                return "An unhandled error occurred";
            default:
                return null;
        }
    }
}

We’ll also need a derived ApiOkResponse class that allows us to return data:

public class ApiOkResponse : ApiResponse
{
    public object Result { get; }

    public ApiOkResponse(object result)
        :base(200)
    {
        Result = result;
    }
}

Finally, let’s declare an ApiBadRequestResponse class to handle validation errors (if we want our responses to be consistent, we will need to replace the built-in functionality used above).

public class ApiBadRequestResponse : ApiResponse
{
    public IEnumerable<string> Errors { get; }

    public ApiBadRequestResponse(ModelStateDictionary modelState)
        : base(400)
    {
        if (modelState.IsValid)
        {
            throw new ArgumentException("ModelState must be invalid", nameof(modelState));
        }

        Errors = modelState.SelectMany(x => x.Value.Errors)
            .Select(x => x.ErrorMessage).ToArray();
    }
}

These classes are very simple but can be customised to your own requirements.

If we change our action to use these ApiResponse based classes, it becomes:

[HttpGet("product")]
public async Task<IActionResult> GetProduct(GetProductRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(new ApiBadRequestResponse(ModelState));
    }

    var model = await _db.Get(...);

    if (model == null)
    {
        return NotFound(new ApiResponse(404, $"Product not found with id {request.ProductId}"));
    }

    return Ok(new ApiOkResponse(model));
}

The code is slightly more complicated now but all three types of response from our action (success, bad request and not found) now use the same general structure.

Centralising Validation Logic

Given that validation is something that you do in practically every action, it makes to refactor this generic code into an action filter. This reduces the size of our actions, removes duplicated code and improves consistency.

public class ApiValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(new ApiBadRequestResponse(context.ModelState));
        }

        base.OnActionExecuting(context);
    }
}

Handling global errors

Responding to bad input in our controller actions is the best way to provide specific error information to our client. Sometimes however, we need to respond to more generic issues. Examples of this include:

  • A 401 Unauthorized code returned from security middleware.

  • A request URL that does not map to a controller action resulting in a 404.

  • Global exceptions. Unless you can do something about a specific exception, you should not clutter your actions with try catch blocks.

As with MVC, the easiest way to deal with global errors is by using StatusCodePagesWithReExecute and UseExceptionHandler.

We talked about StatusCodePagesWithReExecute last time but to reiterate, when a non-success status code is returned from inner middleware (such as an API action), the middleware allows you to execute another action to deal with the status code and return a custom response.

UseExceptionHandler works in a similar way, catching and logging unhandled exceptions and allowing you to execute another action to handle the error. In this example, we configure both pieces of middleware to point to the same action.

We add the middleware in startup.cs:

app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseExceptionHandler("/error/500");
...
//register other middleware that might return a non-success status code

Then we add our error handling action:

[Route("error/{code}")]
public IActionResult Error(int code)
{
    return new ObjectResult(new ApiResponse(code));
}

With this in place, all exceptions and non-success status codes (without a response body) will be handled by our error action where we return our standard ApiResponse.

Custom Middleware

For the ultimate in control, you can replace or complement built-in middleware with your own custom middleware. The example below handles any bodiless response and returns our simple ApiResponse object as JSON. If this is used in conjunction with code in our actions to return ApiResponse objects, we can ensure that both success and failure responses share the same common structure and all requests result in both a status code and a consistent JSON body:

public class ErrorWrappingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorWrappingMiddleware> _logger;
    
    public ErrorWrappingMiddleware(RequestDelegate next, ILogger<ErrorWrappingMiddleware> logger)
    {
        _next = next;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
        }
        catch(Exception ex)
        {
            _logger.LogError(EventIds.GlobalException, ex, ex.Message);

            context.Response.StatusCode = 500;
        }            

        if (!context.Response.HasStarted)
        {
            context.Response.ContentType = "application/json";

            var response = new ApiResponse(context.Response.StatusCode);

            var json = JsonConvert.SerializeObject(response);

            await context.Response.WriteAsync(json);
        }            
    }
}

Conclusion

Handling errors in ASP.NET Core APIs is similar but different from MVC error code. At the action level, we want to return custom objects (serialised as JSON) rather than custom views.

For generic errors, we can still use the StatusCodePagesWithReExecute middleware but need to modify our code to return an ObjectResult instead of a ViewResult.

For full control, it is not difficult to write your own middleware to handle errors exactly as required.

Useful or Interesting?

If you liked the article, I would really appreciate it if you could share it with your Twitter followers.

Share
on Twitter

Comments

Return HTTP Status Codes from ASPNET Core Methods 1

In this article, we will see how to return HTTP Status codes in .NET Core methods based on the HTTP Operation in API.

We shall also see the most commonly asked format like returning HTTP Status code 500 Internal server error etc.

We will cover the below aspects in today’s article,

  • Built-in HTTPS Status Code for .NET Core
  • HTTP Status Code – 200 (OK)
    • What is HTTP Status Ok or 200?
  • HTTP Status Code – 400 (Bad Request)
  • HTTP Status Code – 500 (InternalServerError )
  • Return HTTP 500 using the Status code
  • Using ProblemDetails and Best practices
  • Unhandled exception in API Pipeline?
  • Summary

Example: One may return Web API with ASP.NET Core MVC 200, 500, or 503 error codes

RESTFul services principle talks about HTTP Status code and their usage. It’s important to follow these REST HTTP Status codes appropriately when following REST specifications.

.NET Core has inbuilt support for all basic HTTP Status codes and they are easy to configure for almost all standard HTTP status codes.

RFC7231 defines the specification for this HTTP Status code in detail.

References:

  • https://tools.ietf.org/html/rfc7231#section-6.5.1

RFC7807 defines the specification for error response for HTTP error operation

References:

  • RFC7807 Problem Details HTTP error – Guidelines and Best practices

Built-in HTTPS Status Code for .NET Core

Please note that ASP.NET core provides built-in support for both RFC7231 and RFC7807, giving you direct capability on returning the standards HTTP status code plus additional details in case of error operations based on RFC specifications.

HTTP Status Code – 200 (OK)

If your controller method returns IActionResult,

What is HTTP Status Ok or 200?

  • The HTTP 200 (OK) status code indicates success.
  • This response also meant the response has the payload.
  • If no payload is desired, the server should send a 204 (NoContent) status code.

One can use the built-in type Ok() as below,

       [HttpGet]
        public IActionResult GetSynchrounous(string id)
        {
            Book book = null;
            //run method - Synchronously 
            book = _bookService.Get(id);
            return Ok(book);
        }

This built-in type does support sending additional details with its overloaded API. Please do leverage those when required.

image 11

HTTP Status Code – 400 (Bad Request)

This status code means the server cannot process the request due to malformed requests, invalid requests, etc.

One can use the built-in type BadRequest() as below,

Example:

        [HttpGet("book/{id}")]
        public IActionResult GetAccount(string id)
        {
            if (id == null)
            {
                return BadRequest();
            }
            return Ok();
        }

Below is the response for the above request (Using Chrome) will produce Status Code: 400 Bad Request,

Request URL: http://localhost:60213/api/books/book/1
Request Method: GET
Status Code: 400 Bad Request
Remote Address: [::1]:60213
Referrer Policy: no-referrer-when-downgrade

This built-in type does support sending additional details with its overloaded API.

The below response is based on RFC7807 which is a specification around how to use ProblemDetails.

Return HttpStatus code using Problem Details NET HTTP RFC 7807

Note: Controller method returning IActionResult has built-in supported Error response based on the RFC7807 Porblem details specifications.

HTTP Status Code – 500 (InternalServerError )

This status code means the server encountered an unexpected condition that prevented it from fulfilling the input request.

  • Use built-in type StatusCode()
  • Use Problem() or ProblemDetails() as per RFC7807

Return HTTP 500 using the Status code

Return HTTP 500 using the Status code below,

       
public IActionResult GetAccount1(string id)
        {
            Book book = null;
            try
            {
                //run method - Synchronously 
                book = _bookService.Get(id);

                if (book == null)
                {
                    return NotFound();
                }
                book.Price = 10;
            }
            catch (Exception ex)
            {
              return StatusCode(500, "Internal Server Error. Something went Wrong!");
            }
            return Ok(book);

Using ProblemDetails and Best practices

You can return the HTTP status code using ProblemDetails. Kindly visit the below guidelines for more details,

  • Using ProblemDetails in ASP.NET Core – Guidelines

Return HTTP 500 using ProblemDetails,

           catch (Exception ex)
            {
              return Problem("Internal Server Error.Something went Wrong!");
            }


Below is the response for the above request (Using Chrome) which produces Status Code: 500 Internal Server Error

image 12

Client-side,

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
    "title": "An error occured while processing your request.",
    "status": 500,
    "detail": "Internal Server Error.Something went Wrong!",
    "traceId": "|5b367172-44fadfb7f6df04a9."
}

This built-in type does support sending additional details with its overloaded API.

  • StatusCode( int statusCode, object value);
  • StatusCode( int statusCode);

image 14

StatusCode can be used as an alternative for all other HTTP status codes where the built-in type is missing.

StatusCode class caN be used for all below-supported codes,

 public static class StatusCodes
    {
        public const int Status100Continue = 100;
        public const int Status412PreconditionFailed = 412;
        public const int Status413PayloadTooLarge = 413;
        public const int Status413RequestEntityTooLarge = 413;
        public const int Status414RequestUriTooLong = 414;
        public const int Status414UriTooLong = 414;
        public const int Status415UnsupportedMediaType = 415;
        public const int Status416RangeNotSatisfiable = 416;
        public const int Status416RequestedRangeNotSatisfiable = 416;
        public const int Status417ExpectationFailed = 417;
        public const int Status418ImATeapot = 418;
        public const int Status419AuthenticationTimeout = 419;
        public const int Status421MisdirectedRequest = 421;
        public const int Status422UnprocessableEntity = 422;
        public const int Status423Locked = 423;
        public const int Status424FailedDependency = 424;
        public const int Status426UpgradeRequired = 426;
        public const int Status428PreconditionRequired = 428;
        public const int Status429TooManyRequests = 429;
        public const int Status431RequestHeaderFieldsTooLarge = 431;
        public const int Status451UnavailableForLegalReasons = 451;
        public const int Status500InternalServerError = 500;
        public const int Status501NotImplemented = 501;
        public const int Status502BadGateway = 502;
        public const int Status503ServiceUnavailable = 503;
        public const int Status504GatewayTimeout = 504;
        public const int Status505HttpVersionNotsupported = 505;
        public const int Status506VariantAlsoNegotiates = 506;
        public const int Status507InsufficientStorage = 507;
        public const int Status508LoopDetected = 508;
        public const int Status411LengthRequired = 411;
        public const int Status510NotExtended = 510;
        public const int Status410Gone = 410;
        public const int Status408RequestTimeout = 408;
        public const int Status101SwitchingProtocols = 101;
        public const int Status102Processing = 102;
        public const int Status200OK = 200;
        public const int Status201Created = 201;
        public const int Status202Accepted = 202;
        public const int Status203NonAuthoritative = 203;
        public const int Status204NoContent = 204;
        public const int Status205ResetContent = 205;
        public const int Status206PartialContent = 206;
        public const int Status207MultiStatus = 207;
        public const int Status208AlreadyReported = 208;
        public const int Status226IMUsed = 226;
        public const int Status300MultipleChoices = 300;
        public const int Status301MovedPermanently = 301;
        public const int Status302Found = 302;
        public const int Status303SeeOther = 303;
        public const int Status304NotModified = 304;
        public const int Status305UseProxy = 305;
        public const int Status306SwitchProxy = 306;
        public const int Status307TemporaryRedirect = 307;
        public const int Status308PermanentRedirect = 308;
        public const int Status400BadRequest = 400;
        public const int Status401Unauthorized = 401;
        public const int Status402PaymentRequired = 402;
        public const int Status403Forbidden = 403;
        public const int Status404NotFound = 404;
        public const int Status405MethodNotAllowed = 405;
        public const int Status406NotAcceptable = 406;
        public const int Status407ProxyAuthenticationRequired = 407;
        public const int Status409Conflict = 409;
        public const int Status511NetworkAuthenticationRequired = 511;
    }

Unhandled exception in API Pipeline?

It’s recommended to use the Global exception middleware or handler approach for any unhandled exception in the API pipeline.

Users should vary on what exception should reach the consumer as a business exception Vs what can be logged in the form of technical server logging.

Kindly see below the reference article on best practices of exception handling,

  • Best Practices for Exception Handling in .NET Core
  • RFC7807 Problem Details HTTP error – Guidelines and Best practices

Do you have any comments or ideas or any better suggestions to share?

Please sound off your comments below.

Happy Coding !!

References :

  • API Versioning Best practices in ASP.NET Core with Examples
  • Global Exception Handling in ASP.NET Core using Middleware component

Summary

It’s important to follow HTTP Status codes appropriately when following REST specifications for any communication between client and servicer. Today we learned that .NET Core has provided support for using almost all HTTP status codes allowing us to follow specifications and principles around the same based on standards RFC guidelines.


Please bookmark this page and share it with your friends. Please Subscribe to the blog to get a notification on freshly published best practices and guidelines for software design and development.


Понравилась статья? Поделить с друзьями:
  • Web error 104
  • Web api response error
  • Web api error not found
  • Web api 500 internal server error
  • Web compiler found an error in compilerconfig json