Keywords: ASP.NET MVC | JSON Error Handling | HTTP Status Codes
Abstract: This article delves into how to elegantly return JSON responses with error status codes in the ASP.NET MVC framework to support client-side JavaScript AJAX error handling. By analyzing best practices, it details core methods such as custom JsonResult classes, exception filter mechanisms, and IIS configuration, providing complete code examples and implementation steps to help developers build robust web applications.
Introduction
In modern web development, the separation of front-end and back-end architectures has become increasingly prevalent, with client-side JavaScript using AJAX technology for asynchronous communication with the server as a standard practice. In this context, the server needs to return structured error information so that the client can take appropriate actions based on different error status codes. However, in the ASP.NET MVC framework, directly setting Response.StatusCode and returning a JSON object may lead to issues where the response content is overwritten or lost. This article aims to address this challenge by providing an elegant and maintainable solution.
Problem Background
Developers often attempt to set HTTP status codes and return JSON objects in controller actions, for example:
if (response.errors.Length > 0)
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(response);But this approach may cause the JSON object to be lost in the response, returning only the status code. This limits the client's ability to handle errors in callbacks, as detailed error information cannot be retrieved. Therefore, a mechanism is needed to ensure that JSON data is correctly transmitted to the client while setting error status codes.
Core Solution: Custom JsonResult Class
An efficient solution is to extend ASP.NET MVC's JsonResult class by creating a custom class, JsonHttpStatusResult, which allows specifying an HTTP status code when returning JSON. Here is the implementation code:
public class JsonHttpStatusResult : JsonResult
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(object data, HttpStatusCode httpStatus)
{
Data = data;
_httpStatus = httpStatus;
}
public override void ExecuteResult(ControllerContext context)
{
context.RequestContext.HttpContext.Response.StatusCode = (int)_httpStatus;
base.ExecuteResult(context);
}
}In the controller, it can be used as follows:
if (thereWereErrors)
{
var errorModel = new { error = "There was an error" };
return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}This method is straightforward, overriding the ExecuteResult method to set the status code before the base class execution, ensuring that JSON data and status codes are returned simultaneously.
Advanced Solution: Exception Filter Mechanism
For more complex error handling scenarios, especially those requiring global exception management, a combination of custom exceptions and filters can be implemented. First, define a ValidationException class to encapsulate JSON error information:
class ValidationException : ApplicationException
{
public JsonResult exceptionDetails;
public ValidationException(JsonResult exceptionDetails)
{
this.exceptionDetails = exceptionDetails;
}
public ValidationException(string message) : base(message) { }
public ValidationException(string message, Exception inner) : base(message, inner) { }
protected ValidationException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}Next, create an exception filter, HandleUIExceptionAttribute, implementing the IExceptionFilter interface:
public class HandleUIExceptionAttribute : FilterAttribute, IExceptionFilter
{
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
filterContext.Result = ((ValidationException)filterContext.Exception).exceptionDetails;
}
}
}In the controller action, trigger error handling by throwing a ValidationException:
[HandleUIException]
public JsonResult UpdateName(string objectToUpdate)
{
var response = myClient.ValidateObject(objectToUpdate);
if (response.errors.Length > 0)
throw new ValidationException(Json(response));
}When an exception is thrown, the filter catches it, sets the status code to 500, and returns the JSON error object, ensuring the client receives the data in the error callback.
Supplementary Method: IIS Configuration Adjustment
In some cases, IIS may override custom response content. This can be adjusted by modifying the web.config file:
<system.webServer>
<httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough"/>
</system.webServer>Setting existingResponse="PassThrough" allows custom responses to pass through, preventing IIS's default error pages from overwriting JSON data.
Client-Side Handling Example
In JavaScript, using jQuery AJAX, error responses can be handled as follows:
$.ajax({
type: "POST",
dataType: "json",
url: "MyController/Create",
data: JSON.stringify(myObject),
success: function (result) {
if (result.IsCreated) {
// Handle success logic
} else {
// Handle business logic errors
}
},
error: function (error) {
alert("Error:" + error.responseJSON.ErrorMessage); // Display error message
}
});Through error.responseJSON, the JSON error object returned by the server can be accessed, enabling fine-grained error handling.
Conclusion and Best Practices
When returning JSON error status codes in ASP.NET MVC, it is recommended to use custom JsonResult classes or exception filter mechanisms. The former is suitable for simple error returns, while the latter is better for global exception management and complex business logic. Additionally, be mindful of potential impacts from IIS configuration to ensure response content is not overwritten. These methods not only improve code maintainability but also enhance the flexibility of client-side error handling, aligning with standard practices in modern web development.