Error Handling In ASP.NET Core Web API

Error Handling In ASP.NET Core Web API

Why there is a need for an alternate approach?

In .Net Core, MVC and Web API have been mixed; hence you have similar controllers for both MVC and API activities. In spite of the resemblances, with regard to dealing with errors, it is more likely to have a desire to utilize an alternate approach for API errors.

MVC activities are executed because of a client action in the program so restoring a page of error to the program is the right approach. With an API, this is not the general situation.

The programming interface summons frequently back-end code or javascript code and in either case, you never need to just show the response from the API. Rather you could check the status code and analyze the reaction to decide whether the activity was effective, showing information to the user as essential. An error page is not useful in such circumstances. It increases the response with HTML and makes customer code problematic on the grounds that JSON (or XML) is normal, not HTML.

While we have to return data in an alternate format for Web API activities, the procedures for taking care of errors are not the same as MVC.

The minimalistic approach

With MVC activities, the inability to show a friendly page of error is unsatisfactory in a good application. With an API, while not perfect, empty response bodies are significantly more admissible for some invalid demand sorts. Mainly restoring a 404 status code for an API pathway that does not exist might give the customer enough data to settle their code.

With no configuration, this is the thing that ASP.NET Core gives you.

Based upon your necessities, this might be satisfactory for some basic codes however it will once in a while be sufficient for approval failures. In the event that a customer passes you invalid information, getting a 400 Bad Request is not sufficiently useful for the customer to find the issue. At the very least, you have to tell them which fields are inaccurate and you would restore an enlightening message for every disappointment.

With ASP.NET Web API, this is trifling. Expecting that you are utilizing model official, you get approval for nothing by utilizing information explanations as well as IValidatableObject. To restore the approval data to the customer as JSON is one simple line of code.

Here is a model:

public class GetProductRequest: IValidatableObject

{

[Required]

public string ProductId { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validation context)

{

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 leads to a 400 status code plus a JSON response body like the following:

{

“ProductId”:[“The ProductId field is required.”]

}

This offers a minimum for a customer to use your service and it is not hard to enhance this pattern and make an improved customer experience.

Returning extra information for particular errors

To be honest, it is easy to give extra information if we think a status code approach is a basic element. This is profoundly suggested. There are numerous circumstances where a status code by itself cannot decide the reason for failure. If we consider 404 status codes, for instance, this means:

• We are making a request to the wrong site (might be the “www” site as opposed to the “api” subdomain)

• The domain is right but the URL does not match any route.

• The URL takes the correct path but the resource doesn’t exist.

Here is an endeavor at managing the situation:

[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 currently restoring a more valuable message yet it is a long way to be ideal. The principle concern is that by using a string in the NotFound process, the structure will restore this string as a plain content response and not JSON.

As a customer, an admin returning an alternate type of error is more difficult to manage than a reliable JSON service.

This issue could rapidly be solved by changing the code to what appears beneath, but in the next segment, we will discuss a better option.

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

Customizing the response structure for stability

Creating unknown objects on the go is not the way to deal with; them if you need a steady customer experience. In a perfect way, the API should give back a similar response structure in all cases, even when the request is not successful.

Let’s explain a base ApiResponse class:

public class ApiResponse

{

public int StatusCode { get; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]

public string Message { get; }

public ApiResponse(int statusCode, 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;

}

}

}

There is also a requirement for a derived ApiOkResponse class that allows returning data:

public class ApiOkResponse: ApiResponse

{

public object Result { get; }

public ApiOkResponse(object result)

:base(200)

{

Result = result;

}

}

Finally, let’s announce an ApiBadRequestResponse class to handle validation errors.

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 codes are quite simple but can be customized as per requirements.

If action is changed 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 a little more tricky.

Centralising Logic Of Validation

Given that approval is something necessary, it makes to reconsider this code into an activity process. This reduces the measure of our actions, removes copied codes, and enhances consistency.

public class ApiValidationFilterAttribute: ActionFilterAttribute

{

public override void OnActionExecuting(ActionExecutingContext context)

{

if (!context.ModelState.IsValid)

{

context.Result = new BadRequestObjectResult(newApiBadRequestResponse(ModelState)));

}

base.OnActionExecuting(context);

}

}

Handling all errors

Responding to bad contributions to controller activities is an ideal approach to giving particular error data to the customer. Again and again, we have to respond to more nonspecific issues. Cases which include:

• A 401 Unauthorized code comes back from security middleware.

• A request for a URL that does not guide to a controller action hence resulting in a, 404.

• Global special cases. Unless you take care of a particular exception, you should not mess up your actions.

Likewise, with MVC the least demanding approach to manage worldwide errors is by utilizing StatusCodePagesWithReExecute and UseExceptionHandler.

We discussed StatusCodePagesWithReExecute, but just a revision; when a non-achievement status code comes back from internal middleware, the middleware allows you to produce another action to manage the status code and restore a custom response.

UseExceptionHandler works likewise, getting and logging unhandled special cases and enabling you to yield another action to deal with the mistake. Here, we design both the pieces of middleware to point to a similar action.

We add 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:

[HttpGet(“error/{code}”)]

public IActionResult Error(int code)

{

return new ObjectResult(new ApiResponse(code));

}

With this setup, all exemptions and non-achievement status codes (without a reaction body) will be taken care of by our mistake activity where we restore our standard ApiResponse.

Custom Middleware

For definite control, you can supplement built-in middleware with your custom middleware. This handles any response and returns the basic ApiResponse objects as JSON. The fact is that this is utilized as a part in addition to code in the actions to return ApiResponse objects, We can make sure that both achievement and failure responses share a similar structure and that all requests lead to both a status code and a predictable 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, new JsonSerializerSettings

{

ContractResolver = new CamelCasePropertyNamesContractResolver()

});

await context.Response.WriteAsync(json);

}

}

}

Summary:

Managing errors in ASP.NET Core APIs is likewise different from MVC error codes. In the action phase, we want to give back custom objects rather than custom views.

For non-specific errors, you could use the StatusCodePagesWithReExecute middleware but need to edit the code to return an ObjectResult and not a ViewResult.

We conclude now. Keep coding!

If you want to improve your skills in the Dot Net Course and excel in the Dot NET training program; our institute, CRB Tech Solutions would be of great help and support. We offer a well-structured program for the best Dot Net Course. Among many reputed institutes, CRB Tech has created a niche for itself.

Stay connected to CRB Tech for your technical graduation and to remain updated with all the happenings in the world of Dot Net.