In ASP.NET MVC, I have HTTP method
[HttpPost]
public JsonResult SampleMethod(LoginModel prmModel)
{
}
And LoginModel is like:
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
I want the request to fail, if the request body has more fields than expected (Username and Password)
If HTTP Request Body is {Username: 'U', Password: 'P', Dummy:'D' } the request should fail because of 'Dummy' field in my case. (Exception or Bad Request response)
How can I restrict MVC Model Binder to behave this way on certain methods only?
This requirement is not for all methods, for certain models in project.
If you can use Newtonsoft.JSON library, there is MissingMemberHandling property in JsonSerializerSettings. You can write custom model binder to deserialize an object from json using this property, like this:
public class StrictJsonBodyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(string))
{
if (bindingContext.HttpContext.Request.ContentType != "application/json")
{
throw new Exception("invalid content type, application/json is expected");
}
using (var bodyStreamReader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var jsonBody = await bodyStreamReader.ReadToEndAsync().ConfigureAwait(false);
var jsonSerializerSettings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error,
};
var model = JsonConvert.DeserializeObject(jsonBody, bindingContext.ModelType, jsonSerializerSettings);
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
}
Then you could use this model binder with ModelBinderAttribute for specific action argument:
[HttpPost]
public JsonResult SampleMethod([ModelBinder(typeof(StrictJsonBodyModelBinder))] LoginModel prmModel)
{
}
Now, when invalid properties will be passed, JsonConvert will throw error (will be HTTP 500 for users if error will not be handled).
The easy way to implement this requirement is check Request.Form.Keys.Count != 2 or > 2
if (Request.Form.Keys.Count > 2)
{
return View("Error"); // handle error
}
else
{
// handle logic here
}
Solutions:
[HttpPost]
public JsonResult SampleMethod()
{
dynamic prmModel= System.Web.Helpers.Json.Decode((new StreamReader(Request.InputStream).ReadToEnd()));
Newtonsoft.Json.Schema.JsonSchema schema = JsonSchema.Parse(Jsonschema());
Newtonsoft.Json.Linq.JObject user = JObject.Parse(Newtonsoft.Json.JsonConvert.SerializeObject(prmModel));
if (!user.IsValid(schema) || user.Count > 2)
return Json("Bad Request");
}
public string Jsonschema()
{
string schemaJson = #"{
'description': 'A',
'type': 'object',
'properties': {
'UserName':{'type':'string'},
'Password':{'type':'string'}
}
}";
return schemaJson;
}
Related
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:
Currently, my ApiControllers are returning XML as a response, but for a single method, I want to return JSON. i.e. I can't make a global change to force responses as JSON.
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return model;
}
}
I tried doing this, but can't seem to convert my model to a JSON string correctly:
var jsonString = Json(model).ToString();
var response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
return response;
If you can't make a global change to force responses as JSON,
then try:
[Route("api/Player/videos")]
public HttpResponseMessage GetVideoMappings()
{
var model = new MyCarModel();
return Request.CreateResponse(HttpStatusCode.OK,model,Configuration.Formatters.JsonFormatter);
}
OR
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Json(model);
}
If you want to change globally, then first go to YourProject/App_Start/WebApiConfig.cs and add:
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(
config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml"));
at the bottom of the Register method.
Then try:
[Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
The XML is returned instead JSON because the caller is requesting XML. The returned format can be forced to JSON using a filter that adds the header you need and lets MVC resolve the JSON.
public class AcceptHeaderJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
actionContext.Request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
So you can decorate the method you want to force a JSON response with this attribute and keep the same global JSON configuration and serialization as any other method.
Try this ApiController.Ok.
You just do return Ok(model) and change the return type to IHttpActionResult.
Example:
public class CarController : ApiController
{
[System.Web.Mvc.Route("api/Player/videos")]
public IHttpActionResult GetVideoMappings()
{
var model = new MyCarModel();
return Ok(model);
}
}
For API controllers it is up to the caller to determine how the response is created. Unless you specifically add code to force only one type of response. Here is a simple example of an API method and what happens when called requesting XML, or JSON.
public class XmlEampleController : ApiController
{
[HttpPost]
[ActionName("MyOrderAction")]
public HttpResponseMessage MyOrder([FromBody]MyOder order)
{
if (order != null)
{
return Request.CreateResponse<MyOder>(HttpStatusCode.Created, order);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
[Serializable]
public partial class MyOder
{
private string dataField;
public string MyData
{
get
{
return this.dataField;
}
set
{
this.dataField = value;
}
}
}
}
Sample:
Maybe the issue is with WebApiConfig file.
At the end of the file add these 2 lines
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
It is in Project/App_Start/WebApiConfig.cs For asp.net MVC
I have the following question:
Your application will respond to AJAX requests in JSON format. In
order to maximize control over serialization, you will implement a
custom ActionResult class.
You must override the behavior of the Json helper method in your base
controller so that all JSON responses will use the custom result
class. Which class should you inherit?
The response type is JsonResult. Code-wise I'm having a hard time visualizing the structure. When I read "implement" in the question, I thought of an interface so this is what I came up with:
public class CustAction:ActionResult
{
//max control over serialization
}
public interface ICustAction:CustAction
{
}
public controller MyController:ICustAction, JsonResult
{
//override Json() method in here
}
Would the code above be applicable to the question above?
You can override JsonResult, and return custom JsonResult. For example,
StandardJsonResult
public class StandardJsonResult : JsonResult
{
public IList<string> ErrorMessages { get; private set; }
public StandardJsonResult()
{
ErrorMessages = new List<string>();
}
public void AddError(string errorMessage)
{
ErrorMessages.Add(errorMessage);
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
/* If you do not want to serve JSON on HttpGet, uncomment this. */
/*if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("GET access is not allowed. Change the JsonRequestBehavior if you need GET access.");
}*/
var response = context.HttpContext.Response;
response.ContentType = string.IsNullOrEmpty(ContentType) ? "application/json" : ContentType;
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
SerializeData(response);
}
protected virtual void SerializeData(HttpResponseBase response)
{
if (ErrorMessages.Any())
{
var originalData = Data;
Data = new
{
Success = false,
OriginalData = originalData,
ErrorMessage = string.Join("\n", ErrorMessages),
ErrorMessages = ErrorMessages.ToArray()
};
response.StatusCode = 400;
}
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new JsonConverter[]
{
new StringEnumConverter(),
},
};
response.Write(JsonConvert.SerializeObject(Data, settings));
}
}
public class StandardJsonResult<T> : StandardJsonResult
{
public new T Data
{
get { return (T)base.Data; }
set { base.Data = value; }
}
}
Base Controller
public abstract class BaseController : Controller
{
protected StandardJsonResult JsonValidationError()
{
var result = new StandardJsonResult();
foreach (var validationError in ModelState.Values.SelectMany(v => v.Errors))
{
result.AddError(validationError.ErrorMessage);
}
return result;
}
protected StandardJsonResult JsonError(string errorMessage)
{
var result = new StandardJsonResult();
result.AddError(errorMessage);
return result;
}
protected StandardJsonResult<T> JsonSuccess<T>(T data)
{
return new StandardJsonResult<T> { Data = data };
}
}
Usage
public class HomeController : BaseController
{
public ActionResult Index()
{
return JsonResult(null, JsonRequestBehavior.AllowGet)
// Uncomment each segment to test those feature.
/* --- JsonValidationError Result ---
{
"success": false,
"originalData": null,
"errorMessage": "Model state error test 1.\nModel state error test 2.",
"errorMessages": ["Model state error test 1.", "Model state error test 2."]
}
*/
ModelState.AddModelError("", "Model state error test 1.");
ModelState.AddModelError("", "Model state error test 2.");
return JsonValidationError();
/* --- JsonError Result ---
{
"success": false,
"originalData": null,
"errorMessage": "Json error Test.",
"errorMessages": ["Json error Test."]
}
*/
//return JsonError("Json error Test.");
/* --- JsonSuccess Result ---
{
"firstName": "John",
"lastName": "Doe"
}
*/
// return JsonSuccess(new { FirstName = "John", LastName = "Doe"});
}
}
Credit: Building Strongly-typed AngularJS Apps with ASP.NET MVC 5 by Matt Honeycutt
public class customJsonResult : JsonResult
{
//max control over serialization
}
//in the base controller override the Controller.Json helper method:
protected internal override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
{
return new customJsonResult {
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
JsonRequestBehavior = behavior
};
}
I'm looking at the documentation of WebAPI 2, and i'm severely disappointed with the way the action results are architected. I really hope there is a better way.
So documentation says I can return these:
**void** Return empty 204 (No Content)
**HttpResponseMessage** Convert directly to an HTTP response message.
**IHttpActionResult** Call ExecuteAsync to create an HttpResponseMessage, then convert to an HTTP response message.
**Other type** Write the serialized return value into the response body; return 200 (OK).
I don't see a clean way to return an array of items with custom HTTP status code, custom headers and with auto negotiated content though.
What I would like to see is something like
public HttpResult<Item> Post()
{
var item = new Item();
var result = new HttpResult<Item>(item, HttpStatusCode.Created);
result.Headers.Add("header", "header value");
return result;
}
This way I can glance over a method and immediately see whats being returned, and modify status code and headers.
The closest thing I found is NegotiatedContentResult<T>, with weird signature (why does it need an instance of controller?), but there's no way to set custom headers?
Is there a better way ?
The following code should give you everything you want:
[ResponseType(typeof(Item))]
public IHttpActionResult Post()
{
var item = new Item();
HttpContext.Current.Response.AddHeader("Header-Name", "Header Value");
return Content(HttpStatusCode.Created, item);
}
... if you really need to return an array of items ...
[ResponseType(typeof(List<Item>))]
public IHttpActionResult Post()
{
var items = new List<Item>();
// Do something to fill items here...
HttpContext.Current.Response.AddHeader("Item-Count", items.Count.ToString());
return Content(HttpStatusCode.Created, items);
}
I don't think the designers of the web-api intended for controller methods to be fiddling with the headers.
The design pattern seems to be to use DelegatingHandler, ActionFilterAttribute and the ExecuteAsync overridable method of ApiController to handle authentication and response formatting.
So perhaps your logic for message content negotiation should be handled there ?
However if you definitely need to control headers from within your controller method you can do a little set-up to make it work.
To do so you can create your own DelegationHandler that forwards selected headers from your "Inner" response headers:
public class MessageHandlerBranding : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//If we want to forward headers from inner content we can do this:
if (response.Content != null && response.Content.Headers.Any())
{
foreach (var hdr in response.Content.Headers)
{
var keyUpr = hdr.Key.ToUpper(); //Response will not tolerate setting of some header values
if ( keyUpr != "CONTENT-TYPE" && keyUpr != "CONTENT-LENGTH")
{
string val = hdr.Value.Any() ? hdr.Value.FirstOrDefault() : "";
response.Headers.Add(hdr.Key, val);
}
}
}
//Add our branding header to each response
response.Headers.Add("X-Powered-By", "My product");
return response;
}
}
Then you register this handler in your web-api configuration, this is usually in the GlobalConfig.cs file.
config.MessageHandlers.Add(new MessageHandlerBranding());
You could also write your own custom class for the response object like this:
public class ApiQueryResult<T> : IHttpActionResult where T : class
{
public ApiQueryResult(HttpRequestMessage request)
{
this.StatusCode = HttpStatusCode.OK; ;
this.HeadersToAdd = new List<MyStringPair>();
this.Request = request;
}
public HttpStatusCode StatusCode { get; set; }
private List<MyStringPair> HeadersToAdd { get; set; }
public T Content { get; set; }
private HttpRequestMessage Request { get; set; }
public void AddHeaders(string headerKey, string headerValue)
{
this.HeadersToAdd.Add(new MyStringPair(headerKey, headerValue));
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = this.Request.CreateResponse<T>(this.StatusCode, this.Content);
foreach (var hdr in this.HeadersToAdd)
{
response.Content.Headers.Add(hdr.key, hdr.value);
}
return Task.FromResult(response);
}
private class MyStringPair
{
public MyStringPair(string key, string value)
{
this.key = key;
this.value = value;
}
public string key;
public string value;
}
}
And use it like this in your controller:
[HttpGet]
public ApiQueryResult<CustomersView> CustomersViewsRow(int id)
{
var ret = new ApiQueryResult<CustomersView>(this.Request);
ret.Content = this.BLL.GetOneCustomer(id);
ret.AddHeaders("myCustomHkey","myCustomValue");
return ret;
}
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);
}
}