I need to sent custom exceptions message to client.
I have the following code:
in Startup.cs ConfigureServices method
services.AddGrpc(options => options.Interceptors.Add<ErrorInterceptor>());
in ErrorInterceptor.cs
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (ValidationException validationExc)
{
await WriteResponseHeadersAsync(StatusCode.InvalidArgument, translation =>
translation.GetEnumTranslation(validationExc.Error, validationExc.Parameters));
}
catch (Exception)
{
await WriteResponseHeadersAsync(StatusCode.Internal, translation =>
translation.GetEnumTranslation(HttpStatusCode.InternalServerError));
}
return default;
Task WriteResponseHeadersAsync(StatusCode statusCode, Func<ITranslationService, string> getMessage)
{
var httpContext = context.GetHttpContext();
var translationService = httpContext.RequestServices.GetService<ITranslationService>();
var errorMessage = getMessage(translationService);
var responseHeaders = new Metadata
{
{ nameof(errorMessage) , errorMessage },//1) can see in browser's devTools, but not in the code
{ "content-type" , errorMessage },//2) ugly, but works
};
context.Status = new Status(statusCode, errorMessage);//3) not working
return context.WriteResponseHeadersAsync(responseHeaders);//4) alternative?
}
}
in mask-http.service.ts
this.grpcClient.add(request, (error, reply: MaskInfoReply) => {
this.grpcBaseService.handleResponse<MaskInfoReply.AsObject>(error, reply, response => {
const mask = new Mask(response.id, response.name);
callback(mask);
});
});
in grpc-base.service.ts
handleResponse<T>(error: ServiceError,
reply: {
toObject(includeInstance?: boolean): T;
},
func: (response: T) => void) {
if (error) {
const errorMessage = error.metadata.headersMap['content-type'][0];
this.toasterService.openSnackBar(errorMessage, "Ok");
console.error(error);
return;
}
const response = reply.toObject();
func(response);
}
I wanted to send error using Status (comment 3), but it doesn't get changed
I wonder if there is an alternative way to send it not in response headers (comment 4)
I tried to add custom response header (comment 1), but the only one I received in client code was 'content-type' so I decided to overwrite it (comment 2)
I hit the same dead end recently and decided to do it this way:
Create an error model:
message ValidationErrorDto {
// A path leading to a field in the request body.
string field = 1;
// A description of why the request element is bad.
string description = 2;
}
message ErrorSynopsisDto {
string traceTag = 1;
repeated ValidationErrorDto validationErrors = 2;
}
Create an extension for the error model that serializes the object to JSON:
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public static class ErrorSynopsisDtoExtension
{
public static string ToJson(this ErrorSynopsisDto errorSynopsisDto) =>
JsonConvert.SerializeObject(
errorSynopsisDto,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
Create a custom exception that encapsulates error model:
public class OperationException : Exception
{
private readonly List<ValidationErrorDto> validationErrors = new();
public bool HasValidationErrors => this.validationErrors.Count > 0;
public OperationException(string traceTag) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag
}.ToJson() // <- here goes that extension
) => ErrorTag = traceTag;
public OperationException(
string traceTag,
List<ValidationErrorDto> validationErrors
) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag,
ValidationErrors = { validationErrors }
}.ToJson() // <- here goes that extension again
)
{
ErrorTag = traceTag;
this.validationErrors = validationErrors;
}
}
Throw custom exception from service call handlers:
throw new OperationException(
"MY_CUSTOM_VALIDATION_ERROR_CODE",
// the following block can be simplified with a mapper, for reduced boilerplate
new()
{
new()
{
Field = "Profile.FirstName",
Description = "Is Required."
}
}
);
And lastly, the exception interceptor:
public class ExceptionInterceptor : Interceptor
{
private readonly ILogger<ExceptionInterceptor> logger;
public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger) => this.logger = logger;
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation
)
{
try
{
return await continuation(request, context);
}
catch (OperationException ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
if (ex.HasValidationErrors)
{
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
else
{
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
var opEx = new OperationException("MY_CUSTOM_INTERNAL_ERROR_CODE");
throw new RpcException(
new Status(
StatusCode.Internal,
opEx.Message
)
);
}
}
}
On the TypeScript-based frontend, I simply catch RPC errors and hydrate the message like this:
JSON.parse(err.message ?? {}) as ErrorSynopsisDto
I am using Fluent API to validate the payload in my API. Street Address/MailingAddress/Lockbox Address are three different properties in the domain model. I want to make sure in my validation that Street address and mailing address is only passed once.
Correct Payload
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"MailingAddress": {
"city":"NewYork",
"address":"q2453"
},
"LockBoxAddress": {
"city":"Miami",
"address":"q23888"
}
}
Domain Model
public string id{get;set;}
public string name{get;set;}
public Address streetAddress{get;set;}
public Address MailingAddress{get;set;}
public Address LockboxAddress{get;set;}
Incorrect Payload
{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"streetAddress": {
"city":"NewYork",
"address":"q2453"
}
}
I would like the above payload to error out by saying you cannot pass multiple street addresses and I am using Fluent API
Fluent API
RuleFor(x => x.streetAddress).Count(x =>x < 2).When(x => x.streetAddress!= null);
There is no property to get the count for the model. Any Ideas?
You can't make it via Fluent validation API. Because there'll be only one streetAddress property in your domain model after the payload deserialization. On the other hand, your code RuleFor(x => x.streetAddress) returns IRuleBuilderInitial<XModel, Address> instead of IRuleBuilderInitial<XModel, IList<Address>>. To achieve your goal, you should make sure your validation happens before FluentValidation, i.e., validate the payload when deserializing .
Sometimes a client might send a json with duplicated keys, if you're using ASP.NET Core 2.1 or ASP.NET 2.2, it won't fail by default.
How to solve
To reject such a payload, make sure your version of Newtonsoft.Json is 12.0.1 or higher. If you're not sure, just add such a reference in your *.csproj:
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
And then create a custom Model Binder to handle duplicated keys:
public class RejectDuplicatedKeysModelBinder : IModelBinder
{
private JsonLoadSettings _loadSettings = new JsonLoadSettings(){ DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error };
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName ?? bindingContext.OriginalModelName ?? bindingContext.FieldName ?? String.Empty;
var modelType = bindingContext.ModelType;
var req = bindingContext.HttpContext.Request;
var raw = req.Body;
if (raw == null) {
bindingContext.ModelState.AddModelError(modelName, "invalid request body stream");
return Task.CompletedTask;
}
JsonTextReader reader = new JsonTextReader(new StreamReader(raw));
try {
var json = (JObject)JToken.Load(reader, this._loadSettings);
var o = json.ToObject(modelType);
bindingContext.Result = ModelBindingResult.Success(o);
}
catch (JsonReaderException e) {
var msg = $"wrong property with key='{e.Path}': {e.Message}";
bindingContext.ModelState.AddModelError(modelName, msg);
bindingContext.Result = ModelBindingResult.Failed();
}
catch(Exception e) {
bindingContext.ModelState.AddModelError(modelName, e.ToString()); // you might want to custom the error info
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
By this way, the duplicated error info will be added into ModelState.
Test Case
Let's create a action method for test:
[HttpPost]
public IActionResult Test([ModelBinder(typeof(RejectDuplicatedKeysModelBinder))]XModel model){
if(! this.ModelState.IsValid){
var problemDetails = new ValidationProblemDetails(this.ModelState)
{
Status = StatusCodes.Status400BadRequest,
};
return BadRequest(problemDetails);
}
return new JsonResult(model);
}
When there's a json with duplicated keys, we'll get an error like:
We are using the CQRS pattern and we have a problem with fluent validation's error handling. (CQRS pattern # https://lostechies.com/jimmybogard/2015/05/05/cqrs-with-mediatr-and-automapper/ )
public class OtherSpecified : AbstractValidator<Command>
{
public OtherSpecified(ApplicationDbContext context)
{
RuleFor(x => x.Other).NotNull();
}
}
public class DepartmentSpecified : AbstractValidator<Command>
{
public DepartmentSpecified(ApplicationDbContext context)
{
RuleFor(x => x.Department).NotNull();
}
}
Now we inject our validation handler so we can run multiple abstract validator based on info from http://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/. This works but because I can see the rules running in the foreach loop
public TResponse Handle(TRequest request)
{
var context = new ValidationContext(request);
var result = new ValidationResult();
var list = _validators.ToList();
foreach (var validator in list)
{
var results = validator.Validate(request);
foreach (var validationFailure in results.Errors)
{
result.Errors.Add(validationFailure);
//temp testing code below
if (results.Errors.Count > 0)
throw new ValidationException(result.Errors);
}
}
if (result.Errors != null &&
result.Errors.Count > 0)
{
throw new ValidationException(result.Errors);
}
return _inner.Handle(request);
}
The problem is that the fluent validation exception ( ValidationException ) is not handled. The rule errors bubbles up as
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: FluentValidation.ValidationException: Validation
failed:
What I would expect is Fluent validation to handle the error and pass it back to the ajax request in json as validation errors
I catch the ValidationExceptionwith an HandleErrorAttribute and return json to the ajax request.
public class ValidationExceptionHandlerErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//only handle ValidationExceptions
if ((filterContext.Exception as ValidationException) != null)
{
var result = new ContentResult();
//Add errors to Model State so they are handled auto-magically
foreach (var validationsfailures in (filterContext.Exception as ValidationException).Errors)
{
filterContext.Controller.ViewData.ModelState.AddModelError(validationsfailures.PropertyName,validationsfailures.ErrorMessage);
}
//convert to json and return to ajax request
string content = JsonConvert.SerializeObject(filterContext.Controller.ViewData.ModelState,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
result.Content = content;
result.ContentType = "application/json";
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
}
and I wire up here
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
..
..
filters.Add(new ValidationExceptionHandlerErrorAttribute());
}
In ASP.NET 4 MVC5, I had this class that allowed me to return custom responses for unauthenticated responses to JSON endpoints. Here it is.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (IsAjax(filterContext))
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
success = false,
error = "You must be signed in."
}
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
private bool IsAjax(AuthorizationContext filterContext)
{
return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
null;
}
}
However, in MVC6, the new AuthorizeAttribute is no overrides for creating custom IActionResult results. How do I do this in MVC6?
A good point has been made by #blowdart in his comment about whether returning 401/403 should be the expected behaviour. In any case, I have tried a different approach for doing what the OP was asking, modifying the behavior of the default MVC authorization filters so that we return a json when user is unauthorized.
First thing I did was creating a new IAsyncAuthorizationFilter that will format the unauthorized result as a json for ajax request. It will basically:
Wrap an existing filter
Execute the wrapped filter
In case the user is unauthorized by the wrapped filter, return a json for ajax requests
This would be the CustomJsonAuthorizationFilter class:
public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter
{
private AuthorizeFilter wrappedFilter;
public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter)
{
this.wrappedFilter = wrappedFilter;
}
public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
await this.wrappedFilter.OnAuthorizationAsync(context);
if(context.Result != null && IsAjaxRequest(context))
{
context.Result = new JsonResult(new
{
success = false,
error = "You must be signed in."
});
}
return;
}
//This could be an extension method of the HttpContext/HttpRequest
private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
Then I have created an IApplicationModelProvider in order to wrap all existing AuthorizeFilter with the new custom filter. The AuthroizeFilter is added by AuthorizationApplicationModelProvider, but the new provider will be run after the default one since the order of the default provider is -990.
public class CustomFilterApplicationModelProvider : IApplicationModelProvider
{
public int Order
{
get { return 0; }
}
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
//Do nothing
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
this.ReplaceFilters(context.Result.Filters);
foreach(var controller in context.Result.Controllers)
{
this.ReplaceFilters(controller.Filters);
foreach (var action in controller.Actions)
{
this.ReplaceFilters(action.Filters);
}
}
}
private void ReplaceFilters(IList<IFilterMetadata> filters)
{
var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList();
foreach (var filter in authorizationFilters)
{
filters.Remove(filter);
filters.Add(new CustomJsonAuthorizationFilter(filter));
}
}
}
Finally, update ConfigureServices in startup with the new application model provider:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>());
I finally figured it out after looking at the source.
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
Func<CookieRedirectContext, Task> _old;
public CustomCookieAuthenticationEvents()
{
_old = OnRedirectToLogin;
OnRedirectToLogin = OnCustomRedirectToLogin;
}
public Task OnCustomRedirectToLogin(CookieRedirectContext context)
{
var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
if (actionContext.ActionContext == null)
return _old(context);
if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
{
// this is an ajax request, return custom JSON telling user that they must be authenticated.
var serializerSettings = context
.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcJsonOptions>>()
.Value
.SerializerSettings;
context.Response.ContentType = "application/json";
using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.CloseOutput = false;
var jsonSerializer = JsonSerializer.Create(serializerSettings);
jsonSerializer.Serialize(jsonWriter, new
{
success = false,
error = "You must be signed in."
});
}
}
return Task.FromResult(0);
}
else
{
// this is a normal request to an endpoint that is secured.
// do what ASP.NET used to do.
return _old(context);
}
}
}
Then, use this event class as follows:
services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
});
ASP.NET 5 sure made simple things harder to do. Granted though, I can now customize things at a more granular level without effecting other pieces. Also, the source code is amazingly easy to read/understand. I am pretty happy having the confidence that any issue I am having can easily be identified as a bug or resolved by looking at the source.
Cheers to the future!
I was trying to return an error to the call to the controller as advised in
This link so that client can take appropriate action.
The controller is called by javascript via jquery AJAX. I am getting the Json object back only if I don't set the status to error.
Here is the sample code
if (response.errors.Length > 0)
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(response);
I get the Json if I don't set the statuscode.
If I set the status code I get the status code back but not the Json error object.
Update
I want to send an Error object as JSON so that it can be handled error callback of ajax.
The neatest solution I've found is to create your own JsonResult that extends the original implementation and allows you to specify a HttpStatusCode:
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);
}
}
You can then use this in your controller action like so:
if(thereWereErrors)
{
var errorModel = new { error = "There was an error" };
return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}
I found the solution here
I had to create a action filter to override the default behaviour of MVC
Here is my exception class
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) { }
}
Note that I have constructor which initializes my JSON. Here is the action filter
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).myJsonError;
}
}
Now that I have the action filter, I will decorate my controller with the filter attribute
[HandleUIException]
public JsonResult UpdateName(string objectToUpdate)
{
var response = myClient.ValidateObject(objectToUpdate);
if (response.errors.Length > 0)
throw new ValidationException(Json(response));
}
When the error is thrown the action filter which implements IExceptionFilter get called and I get back the Json on the client on error callback.
There is a very elegant solution to this problem, just configure your site via web.config:
<system.webServer>
<httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough"/>
</system.webServer>
Source: https://serverfault.com/questions/123729/iis-is-overriding-my-response-content-if-i-manually-set-the-response-statuscode
A simple way to send a error to Json is control Http Status Code of response object and set a custom error message.
Controller
public JsonResult Create(MyObject myObject)
{
//AllFine
return Json(new { IsCreated = True, Content = ViewGenerator(myObject));
//Use input may be wrong but nothing crashed
return Json(new { IsCreated = False, Content = ViewGenerator(myObject));
//Error
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return Json(new { IsCreated = false, ErrorMessage = 'My error message');
}
JS
$.ajax({
type: "POST",
dataType: "json",
url: "MyController/Create",
data: JSON.stringify(myObject),
success: function (result) {
if(result.IsCreated)
{
//... ALL FINE
}
else
{
//... Use input may be wrong but nothing crashed
}
},
error: function (error) {
alert("Error:" + erro.responseJSON.ErrorMessage ); //Error
}
});
Building on the answer from Richard Garside, here's the ASP.Net Core version
public class JsonErrorResult : JsonResult
{
private readonly HttpStatusCode _statusCode;
public JsonErrorResult(object json) : this(json, HttpStatusCode.InternalServerError)
{
}
public JsonErrorResult(object json, HttpStatusCode statusCode) : base(json)
{
_statusCode = statusCode;
}
public override void ExecuteResult(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_statusCode;
base.ExecuteResult(context);
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_statusCode;
return base.ExecuteResultAsync(context);
}
}
Then in your controller, return as follows:
// Set a json object to return. The status code defaults to 500
return new JsonErrorResult(new { message = "Sorry, an internal error occurred."});
// Or you can override the status code
return new JsonErrorResult(new { foo = "bar"}, HttpStatusCode.NotFound);
The thing that worked for me (and that I took from another stackoverflow response), is to set the flag:
Response.TrySkipIisCustomErrors = true;
You have to return JSON error object yourself after setting the StatusCode, like so ...
if (BadRequest)
{
Dictionary<string, object> error = new Dictionary<string, object>();
error.Add("ErrorCode", -1);
error.Add("ErrorMessage", "Something really bad happened");
return Json(error);
}
Another way is to have a JsonErrorModel and populate it
public class JsonErrorModel
{
public int ErrorCode { get; set;}
public string ErrorMessage { get; set; }
}
public ActionResult SomeMethod()
{
if (BadRequest)
{
var error = new JsonErrorModel
{
ErrorCode = -1,
ErrorMessage = "Something really bad happened"
};
return Json(error);
}
//Return valid response
}
Take a look at the answer here as well
You need to decide if you want "HTTP level error" (that what error codes are for) or "application level error" (that what your custom JSON response is for).
Most high level objects using HTTP will never look into response stream if error code set to something that is not 2xx (success range). In your case you are explicitly setting error code to failure (I think 403 or 500) and force XMLHttp object to ignore body of the response.
To fix - either handle error conditions on client side or not set error code and return JSON with error information (see Sbossb reply for details).
Several of the responses rely on an exception being thrown and having it handled in the OnException override. In my case, I wanted to return statuses such as bad request if the user, say, had passed in a bad ID. What works for me is to use the ControllerContext:
var jsonResult = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet, Data = "whoops" };
ControllerContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return jsonResult;
And if your needs aren't as complex as Sarath's you can get away with something even simpler:
[MyError]
public JsonResult Error(string objectToUpdate)
{
throw new Exception("ERROR!");
}
public class MyErrorAttribute : 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 = new JsonResult() { Data = filterContext.Exception.Message };
}
}
}
If you are just using MVC the simplest way is to use HttpStatusCodeResult.
public ActionResult MyAjaxRequest(string args)
{
string error_message = string.Empty;
try
{
// successful
return Json(args);
}
catch (Exception e)
{
error_message = e.Message;
}
return new HttpStatusCodeResult(500, error_message);
}
When the error is returned to the client you can display it or action it how you like.
request.fail(function (jqXHR) {
if (jqXHR.status == 500) {
alert(jqXHR.statusText);
}
})
I was running Asp.Net Web Api 5.2.7 and it looks like the JsonResult class has changed to use generics and an asynchronous execute method. I ended up altering Richard Garside's solution:
public class JsonHttpStatusResult<T> : JsonResult<T>
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(T content, JsonSerializerSettings serializer, Encoding encoding, ApiController controller, HttpStatusCode httpStatus)
: base(content, serializer, encoding, controller)
{
_httpStatus = httpStatus;
}
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var returnTask = base.ExecuteAsync(cancellationToken);
returnTask.Result.StatusCode = HttpStatusCode.BadRequest;
return returnTask;
}
}
Following Richard's example, you could then use this class like this:
if(thereWereErrors)
{
var errorModel = new CustomErrorModel("There was an error");
return new JsonHttpStatusResult<CustomErrorModel>(errorModel, new JsonSerializerSettings(), new UTF8Encoding(), this, HttpStatusCode.InternalServerError);
}
Unfortunately, you can't use an anonymous type for the content, as you need to pass a concrete type (ex: CustomErrorType) to the JsonHttpStatusResult initializer. If you want to use anonymous types, or you just want to be really slick, you can build on this solution by subclassing ApiController to add an HttpStatusCode param to the Json methods :)
public abstract class MyApiController : ApiController
{
protected internal virtual JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings, Encoding encoding)
{
return new JsonHttpStatusResult<T>(content, httpStatus, serializerSettings, encoding, this);
}
protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings)
{
return Json(content, httpStatus, serializerSettings, new UTF8Encoding());
}
protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus)
{
return Json(content, httpStatus, new JsonSerializerSettings());
}
}
Then you can use it with an anonymous type like this:
if(thereWereErrors)
{
var errorModel = new { error = "There was an error" };
return Json(errorModel, HttpStatusCode.InternalServerError);
}
Here is the JsonResult override answer for ASP.NET v5+ . I have tested and it works just as well as in earlier versions.
public class JsonHttpStatusResult : JsonResult
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(object data, HttpStatusCode httpStatus) : base(data)
{
_httpStatus = httpStatus;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_httpStatus;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var services = context.HttpContext.RequestServices;
var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();
return executor.ExecuteAsync(context, this);
}
}