Catch/translate exception from ASP.NET OData Get method - c#

I have an OData service implemented, using ASP.NET OData library.
So, I have a controller like this one:
public class ProjectsController : ODataController
{
private readonly MyContext _db;
public ProjectsController(MyContext db)
{
_db = db;
}
[EnableQuery]
public IQueryable<Project> Get(string customQuery)
{
var query = _db.Projects;
if (!string.IsNullOrWhitespace(customQuery))
{
query = query.Where(/* something complex going here */);
}
return query.OrderByDescending(p => p.Id);
}
}
Now, all this works fine. But, in some cases, some specific "customQuery" may produce SQL code which results in division by zero. And, as result of that, the server sends back status 500 (oops) and the error object like this one:
{"error":{"code":"","message":"An error has occurred."}}
This is not very informative. I want to catch the exception and translate it 400 with some meaningful message (advising the user how to fix the custom query).
I've tried setting global exception filter, attribute exception filter.. no luck. Any ideas?

In case anyone is interested the way to go was to implement my own IExceptionHandler:
class MyExceptionHandler : IExceptionHandler
{
public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
if (context.Request.Method == HttpMethod.Get && context.Request.RequestUri.AbsolutePath == "/odata/projects")
{
if (IsDivideByZero(context.Exception))
{
const string message = "Division by zero encountered while applying the filter";
var response = context.Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError(message));
context.Result = new ResponseMessageResult(response);
}
}
return Task.CompletedTask;
}
private static bool IsDivideByZero(Exception ex)
{
if (ex is SqlException sqlEx && sqlEx.Number == 8134)
return true;
return ex.InnerException != null && IsDivideByZero(ex.InnerException);
}
}
and register it with DI like this:
private static void Configure(HttpConfiguration config)
{
// ...
config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
// ...
}

Related

How to handle "undefined" or "null" in ASP.NET Core web API from frontend React.js?

im in the process of writing ASP.NET Core REST API. here is controller method and this method should get selected when incoming route was matched.
controller.cs
[HttpGet]
[Route("/api/Account/{accountID}/{productIds}/files")]
public ActionResult<IEnumerable<FilesDto>> GetFiles(long accountID, string productIds)
{
using (new MethodCallLogger(logger, UserContext))
{
return Ok(FilesService.GetFiles(accountID, productIds));
}
}
FilesService.cs
public IEnumerable<FilesDto> GetFiles(long accountID, string productIds)
{
// in some cases productIds will come empty
if(!string.isNullorEmpty(productIds)
{
}
}
in some cases productIds will come empty, to select all the products.
if i pass null or empty string from FrontEnd Reactjs, here its receiving "null" or "undefined" as string value. so the string.isNullorEmpty condition get passed and throwing error, like invalid produtIDs.
so, what is the perfect to handle this "null" or "undefined". or how can we pass empty string or null from reactjs to backend.
Thanks for your time.
You can use "all" in productIds for example:
/api/Account/55/all/files
Or you can also change the structure of the api for example:
/api/Account/files/{accountID}/{productIds}
And productIds can be "all" or Number
or you can use queryParams for productIds like:
/api/Account/{accountID}/files?productIds=Number
for example:
/api/Account/20/files?productIds=4
and when productIds not sended, you send all files for specific accountID
1./api/Account/20/files
Or
2./api/Account/20/files?productIds=all
Create a NotFoundApiException
public class NotFoundApiException : Exception
{
public NotFoundApiException(string message) : base(message) { }
public NotFoundApiException(string message, params object[] args)
: base(String.Format(CultureInfo.CurrentCulture, message, args))
{
}
}
Create A Custom Error handling Middleware
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
string erorMessage = error.Message;
switch (error)
{
case NotFoundApiException e:
// custom not-found application error
errorMessage = e.Message;
response.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
// unhandled error
response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
var result = JsonConvert.SerializeObject(new
{
message = errorMessage
});
await response.WriteAsync(result);
}
}
}
Then use the Middleware in Startup/Program.cs :
app.UseMiddleware<ErrorHandlerMiddleware>();
Then simply throw this exception when value is null by :
if(value is null)
throw new NotFoundApiException("Data is null")

Azure Function Middleware: How to return a custom HTTP response?

I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.
I have built a dummy middleware like this one:
public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionLoggingMiddleware> m_logger;
public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
{
m_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception unhandledException)
{
m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
}
}
}
In my use case, the Azure Function is an HTTP triggered function:
public sealed class StorageAccountsFunction
{
private readonly ILogger<StorageAccountsFunction> m_logger;
public StorageAccountsFunction
(
ILogger<StorageAccountsFunction> logger
)
{
m_logger = logger;
}
[Function("v1-post-storage-account")]
public async Task<HttpResponseData> CreateAsync
(
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")]
HttpRequestData httpRequestData,
FunctionContext context
)
{
m_logger.LogInformation("Processing a request to create a new storage account");
throw new Exception("Oh no! Oh well..");
}
}
In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.
I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.
Question
The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?
According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions
We created an extension method for FunctionContext:
internal static class FunctionUtilities
{
internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
result.SetValue(functionBindingsFeature, response);
}
}
The usage in the middleware looks like this:
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
if (ex.InnerException is *NameOfExceptionYouNeed* e)
{
var req = context.GetHttpRequestData();
var res = await req.ErrorResponseAsync(e.Message);
context.InvokeResult(res);
return;
}
throw;
}
}
This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.
The FunctionContextHttpRequestExtensions class was introduced so now you can just
using Microsoft.Azure.Functions.Worker;
public class MyMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// To access the RequestData
var req = await context.GetHttpRequestDataAsync();
// To set the ResponseData
var res = req!.CreateResponse();
await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = res;
}
}
This code works for me. It is based on the example here: https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Simple example which always fails. Use the following in an error condition
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
await newHttpResponse.WriteAsJsonAsync(new { ResponseStatus = "Invocation failed!" }, newHttpResponse.StatusCode);
context.GetInvocationResult().Value = newHttpResponse;
}
}

MediatR CQRS - How to deal with unexisting resources (asp.net core web api)

So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.
If we take DELETE for example, here is my controller action:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});
return NoContent();
}
The Command:
public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}
The Command Handler:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
return Unit.Value;
}
}
As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.
MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:
public class DeleteCourseCommand : IRequest<bool>
...
In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.
Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...
public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...
if (course == null)
return false; // Simple example, where false means it wasn't found.
...
return true;
}
}
The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).
Finally, you'll need to update your Delete action to use the new response type, like this:
public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});
if (!courseWasFound)
return NotFound();
return NoContent();
}
I like returning events from my commands. The command is telling your application what the client wants it to do. The response is what it actually did.
BTW—it's said that command handlers should return anything. That's really only true in a fully async environment where the command won't be completed until sometime after the response to the client that it's accepted. In that case, you would return Task<Unit> and publish these events. The client would get them via some other channel, like a SignalR hub once they were raised. Either way, events are the best way to tell a client what's going on in your application.
Start by defining an interface for your events
public interface IEvent
{
}
Then, create events for each of the things that can happen in a command. You can include information in them if you'd want to do something with that information or just leave them empty if the class itself is enough.
public class CourseNotFoundEvent : IEvent
{
}
public class CourseDeletedEvent : IEvent
{
}
Now, have your command return an event interface.
public class DeleteCourseCommand : IRequest<IEvent>
{
}
Your handler would look something like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course is null)
return new CourseNotFoundEvent();
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
return new CourseDeletedEvent();
}
}
Finally, you can use pattern matching on your web API to do things based on the event that gets returned.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var #event = await Mediator.Send(new DeleteCourseCommand {Id = id});
if(#event is CourseNotFoundEvent)
return NotFound();
return NoContent();
}
I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Result = new JsonResult(
((ValidationException)context.Exception).Failures);
return;
}
var code = HttpStatusCode.InternalServerError;
if (context.Exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new
{
error = new[] { context.Exception.Message }
});
}
}
Startup Class:
services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));
Custom Exception:
public class NotFoundException : Exception
{
public NotFoundException(string entityName, int key)
: base($"Entity {entityName} with primary key {key} was not found.")
{
}
}
Then in the Handle method:
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
else
{
throw new NotFoundException(nameof(Course), request.Id);
}
return Unit.Value;
This seems to do the trick, if anyone can see any potential issues with this please let me know!

DataException from Dapper disappear when using MediatR

I have a strange problem with DataExceptions from Dapper doesn't properagte correctly.
Here's my setup:
public class CustomerController : Controller
{
private readonly IMediator _mediator;
public CustomerController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> Get(Get.Query query)
{
var result = await _mediator.Send(query);
return Ok(result);
}
}
public class Get
{
public class Query : IRequest<IEnumerable<Result>>
{
}
public class Result
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class QueryHandler : IAsyncRequestHandler<Query, IEnumerable<Result>>
{
private readonly IDbConnection _dapper;
public QueryHandler(IDbConnection dapper)
{
_dapper = dapper;
}
public async Task<IEnumerable<Result>> Handle(Query message)
{
// the below throws because of incorrect type mapping
// (yes, the connection is open)
var customers =
await _dapper.Connection.QueryAsync<Result>("SELECT Id, Name FROM [Customer].[Customers]");
return customers;
}
}
}
The result
Curl
curl -X GET 'http://localhost:5000/api/Customer'
Request URL
http://localhost/api/Customer
Response Body
no content
Response Code
500
Expected
I was expecting 500 with an error description and not no content.
This is the exception thrown:
If I change my Handle method to:
public async Task<IEnumerable<Result>> Handle(Query message)
{
throw new DataException("What is going on?");
}
I get the expected result. A 500 with an error saying "What is going on?"
Because I have app.UseDeveloperExceptionPage(); enabled it looks like this.
An unhandled exception occurred while processing the request.
DataException: What is going on?
...Customer.Get+QueryHandler+<Handle>d__2.MoveNext() in Get.cs, line 42
Stack Query Cookies Headers
DataException: What is going on?
...
But that's expected.
So what is going on? Why doesn't the DataException from Dapper work as expected?
MediatR's preprocessor aggreggates all exception from pipeline in AggregateException.
You have to expose it - for instance with ExceptionFilterAttribute:
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
Exception exception = context.Exception;
if (exception is AggregateException
&& exception.InnerException != null)
{
exception = exception.InnerException;
}
// check type and do you stuff ........
context.Response = new HttpResponseMessage
{
Content = this.CreateContent(response),
StatusCode = HttpStatusCode.BadRequest
};
//....

Return JSON with error status code MVC

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

Categories

Resources