I'm trying to upload a form with some text fields and a file to my WebAPI. Currently I always get a 415 error (a breakpoint in the ASP controller doesn't gets hit). My code looks like this:
Angular Service
// 'Upload' is from ng-file-upload
function applicationService(settings, $http, Upload) {
var createCustomApplication = function(application) {
var url = settings.baseUrl + '/api/applications/custom';
var data = new FormData();
angular.forEach(application, function (value, key) {
data.append(key, value);
});
return Upload.upload({
url: url,
data: data,
method: 'POST'
});
};
return {
createCustomApplication: createCustomApplication
}
}
WebAPI controller
[ResponseType(typeof(ApplicationModel))]
[HttpPost, Route("api/applications/custom")]
public IHttpActionResult CreateCustomApplication([FromBody]ApplicationModel application)
{
var file = HttpContext.Current.Request.Files[0];
return Ok();
}
If you want to include a form with files as parameter to an action it is necessary to add a custom media formatter. Fortunately someone already created a Nuget-package for this. Configuration is easy. Install the package and add a line to your WebApiConfig-file.
This package allows you to use a HttpFile-object which captures your file either directly as a parameter or inside a model. From the docs:
[HttpPost]
public void PostFileBindRawFormData(MultipartDataMediaFormatter.Infrastructure.FormData formData)
{
HttpFile file;
formData.TryGetValue(<key>, out file);
}
or
public class PersonModel
{
public string FirstName {get; set;}
public string LastName {get; set;}
public DateTime? BirthDate {get; set;}
public HttpFile AvatarImage {get; set;}
public List<HttpFile> Attachments {get; set;}
public List<PersonModel> ConnectedPersons {get; set;}
}
//api controller example
[HttpPost]
public void PostPerson(PersonModel model)
{
//do something with the model
}
I've had the same issue.
You should add the content type in your POST request.
headers: {
'Content-Type': undefined
},
You're code looks very similar to what I use. Perhaps the issue is to do with the multipart/form-data header value. I'm not experienced enough to say what is wrong in your implementation, but perhaps try this alternate async await approach.
[Route("api/applications/custom")]
[HttpPost]
public async Task<HttpResponseMessage> Upload()
{
MultipartMemoryStreamProvider memoryStreamProvider;
try
{
if (!Request.Content.IsMimeMultipartContent())
{
this.Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
memoryStreamProvider = await Request.Content.ReadAsMultipartAsync();
}
catch (Exception e)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, string.Format("An error has occured while uploading your file. Error details: '{0}'", e.Message));
}
// do something with your file...
return Request.CreateResponse(HttpStatusCode.OK);
}
Here is my JS code. I also use promises instead of what ever is returned (if anything) from the Upload function.
$scope.submit = function() {
if ($scope.form.file.$valid && $scope.file) {
$scope.upload($scope.file);
}
};
$scope.upload = function(file) {
Upload.upload({
url: 'api/applications/custom',
data: { file: file }
}).then(function(response) {
// report the success to the user
}, function(response) {
// report the error to the user
}, function(evt) {
// report the progress of the upload to the user
$scope.uploadProgress = evt.loaded / evt.total;
});
};
I used the following article as a basis for my solution: http://monox.mono-software.com/blog/post/Mono/233/Async-upload-using-angular-file-upload-directive-and-net-WebAPI-service/
Related
I am aware that there is already a similar question, but it didn't solve the above error.
This is my PUT action in my API's Controller and this is working fine in Swagger:
[HttpPut("{id}/upload")]
public async Task<IActionResult> UploadFile(string id, IFormFile file)
{
// codes using id above are omitted, nothing to do with the error
var path = Path.Combine(
Directory.GetCurrentDirectory(),
"wwwroot",
file.FileName
);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Ok();
}
And here are the codes in my Angular web page:
HTML
<input type="file" (change)="setFile($event.target.files)"/>
<button (click)="save()"> Save </button>
TypeScript
forUpload: File = null;
#Input() someModel: SomeModel;
// setting of file
setFile(myFile: File){
if (myFile === null){
return;
}
this.forUpload = myFile[0];
}
// saving
save() {
const addFileFnc = this.theService.addFile.bind(this.theService);
addFileFnc(this.someModel, this.forUpload).subscribe(
() => { this.ref.close(); },
(e) => { console.log(e); }
);
}
And, my service is:
addFile(someModel: SomeModel, myFile: File): Observable<any> {
return this.http.put(`${api_url}/api/somemodels/${someModel.id}/upload`, {
id: someModel.id,
file: myFile
});
}
I also used FormData to try the answer in the said question above, I appended my forUpload File to a FormData but unfortunately, same error. How can I fix this if I don't have any kind of setting in media types?
Your code together with the previous answer almost got it. I'd rather not change the parameters in your controller but there's something to add, that is FromForm attribute. So this will be your new controller:
[HttpPut("{id}/upload")]
public async Task<IActionResult> UploadFile(string id, [FromForm] IFormFile file)
{
// codes here
}
I'd agree with previous answer to use FormData, but I'll spare your setFile() from any changes and just some addition for typescript:
forUpload: File = null;
#Input() someModel: SomeModel;
save() {
const addFileFnc = this.theService.addFile.bind(this.theService);
const formdata = new FormData();
formdata.append("id", this.someModel.id);
formdata.append("file", this.forUpload);
addFileFnc(formdata).subscribe(
() => { this.ref.close(); },
(e) => { console.log(e); }
);
}
Instead of appending the whole someModel, I only did your someModel.id since I noticed that it is the only thing you are using in your service, which will be changed into this:
addFile(formdata: FormData): Observable<any> {
return this.http.put(`${api_url}/api/somemodels/${formdata.get("id")}/upload`, formdata);
}
Your Error 400 Bad Request (as what said in the comments here) is because formdata in the service, based on the previous answer that you are following, is sending a JSONify formdata instead of the FormData itself.
Here is how I upload file in ASP.Net Core and Angular
In the angular side you will need to use FormData to submit
const formdata = new FormData();
formdata.append("SomeProp ", "somestring");
formdata.append("Image ", myFile); // change your code accordingly here
return this.http.put(`${api_url}/api/somemodels/${someModel.id}/upload`, {
formdata
});
In server side
public class PostViewModel
{
public string SomeProp { get; set; }
public IFormFile Image { get; set; }
}
And in my controller I use FromForm attribute
public async Task<IActionResult> SavePost([FromForm]PostViewModel viewModel)
Update:
Make sure you enalbe CORS support in your ASP.Net Core like this
Enabling CORS
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors(
options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
);
app.UseMvc();
}
I have an ASP.NET MVC Controller defined like so:
[HttpPost]
public string uploadQAImage(FileUploadClass fileUploadClass, HttpPostedFile image)
{
}
And this what the class FileUploadClass looks like.
public class FileUploadClass
{
public string job { get; set; }
public string createdBy { get; set; }
public string itemId { get; set; }
}
What I am trying to do with Alamofire in iOS is call this Controller, I have tried using parameters:
func saveQAPhotos(_ cellHolder: PhotoClass, completion: #escaping (_ result: Dictionary<String, Any>) -> Void)
{
let parameters: Parameters = [
"job" : cellHolder.job!,
"itemId" : cellHolder.itemId!,
"createdBy" : appDelegate.username!
]
let urlComponents = NSURLComponents(string: webservice + "uploadQAImage");
urlComponents?.user = appDelegate.username;
urlComponents?.password = appDelegate.password;
let url = urlComponents?.url;
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as String)
}
if let data = cellHolder.photo {
multipartFormData.append(data, withName: "image", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url!, method: .post, headers: nil) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON { response in
if let result = response.result.value {
let jsonData = result as! Dictionary<String, Any>
completion(jsonData)
}
}
case .failure(_):
print("error")
}
}
}
But that didn't work on the ASP.NET side I get this error:
can't bind multiple parameters to the request's content
I have also tried sending the data as [AnyHashable: Any] like so:
func saveQAPhotos(_ cellHolder: PhotoClass, completion: #escaping (_ result: Dictionary<String, Any>) -> Void)
{
var jsonDict = [AnyHashable: Any]()
jsonDict["job"] = cellHolder.job
jsonDict["itemId"] = cellHolder.itemId
jsonDict["createdBy"] = appDelegate.username
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
let urlComponents = NSURLComponents(string: webservice + "uploadQAImage");
urlComponents?.user = appDelegate.username;
urlComponents?.password = appDelegate.password;
let url = urlComponents?.url;
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(jsonData!, withName: "fileUploadClass", mimeType: "application/json")
if let data = cellHolder.photo {
multipartFormData.append(data, withName: "image", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url!, method: .post, headers: nil) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON { response in
if let result = response.result.value {
let jsonData = result as! Dictionary<String, Any>
completion(jsonData)
}
}
case .failure(_):
print("error")
}
}
}
Same error as before
can't bind multiple parameters to the request's content
Now when I change the ASP.NET Controller to only get FileUploadClass like so:
[HttpPost]
public string uploadQAImage(FileUploadClass fileUploadClass)
{
}
I get this error:
Object reference not set to an instance of an object.
I can only assume if I do fix the can't bind multiple parameters to the request's content error I will get this error next.
I am pretty sure I am sending the data incorrectly, so I guess my question is how do I send data from Alamofire upload method to an ASP.NET MVC method?
I am sending an id and a list from angular to C# web API, but it is received by null when debugging the method, without knowing the reason.
angular service
this.deletevacs = function (staffid, vacs) {
return $http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs?staffid=" + staffid + '&vacs=' + JSON.stringify(vacs))
}
angular js
var result = DevExpress.ui.dialog.confirm("Are you sure want to delete vacations assigned employees ?", "Confirm changes");
result.done(function (dialogResult) {
if (dialogResult) {
var promisePost = Assignments.deletevacs($scope.SelectedEmp1.staffkey, $scope.oldvacs);
promisePost.then(function (pl) {
toastr.success("Successfully deleted");
C#
[HttpPost]
public HttpResponseMessage deletevacs(int staffid,int[] vacs)
{
try
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
, staffid
, vacs));
}
catch (Exception ex)
{
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
any help, thanks in advance
new C#
[HttpPost]
public HttpResponseMessage deletevacs([FromBody] role r)
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
,r.Staff_Key
, r.VacMKey));
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
class
public class role
{
//public int Ass_Dep_Key { get; set; }
public int Dep_Key { get; set; }
public int Staff_Key { get; set; }
public int Role { get; set; }
public List<int> VacMKey { get; set; }
public short SelfApprov { get; set; }
}
new angular
var deletevacssss = { Staff_Key: $scope.SelectedEmp1.staffkey, VacMKey: $scope.selectedvacs };
var result = DevExpress.ui.dialog.confirm("Are you sure want to delete vacations assigned employees ?", "Confirm changes");
result.done(function (dialogResult) {
if (dialogResult) {
var promisePost = Assignments.deletevacs(deletevacssss);
promisePost.then(function (pl) {
toastr.success("Successfully deleted");
new angular service
this.deletevacs = function (deletes) {
return $http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs", deletes)
}
when i make a debug, the r object from role class get the staffkey from angular correctly but the list of vacmkey count by 0 ???
you should pass the parameter values for post method in params property .
var data = {staffid: staffid, vacs:vacs};
$http({
url: "/AssignUserToDepartmentapi/api/assignuser/deletevacs",
method: "POST",
params: data
})
or try this below way
var data = {staffid: staffid, vacs:vacs};
$http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs", data ).then(function (data, status, headers, config) {
alert("success");
},function (data, status, headers, config) {
alert("error");
});
API code should be like
[HttpPost]
public HttpResponseMessage deletevacs([FromUri]int staffid, [FromUri] int[] vacs)
{
...
}
Solution 1:
Use [FromUri] attribute in your HttpPost method parameter since you are getting the value from the querystring in the Url.
Try this:
[HttpPost]
public HttpResponseMessage deletevacs([FromUri]int staffid, [FromUri] int[] vacs)
{
try
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
, staffid
, vacs));
}
catch (Exception ex)
{
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Solution 2:
Create an object that contains the staffId and vacs properties and assign it to the parameter. Add the [FromBody] attribute to before the object parameter. Then in your httpPost call, put the parameter inside the body of the request instead of the Url.
Google about the use of [FromBody] in webapi.
Solution 3:
Istead of using HttpPost, why not use HttpDelete method. It is clear in your problem that your intention is to delete records. In web API, you can use the HttpDelete attribute instead of HttpPost. To do this, change your attribute to HttpDelete from HttpPost, and in your paramter, you can just pass the [FromUri] int StaffId only and inside your API, all the vacs record related that StaffId, you can delete programmatically in DB. When calling your API use the $http.delete endpoint method instead.
I am using .net core C#, WebApi & AngularJs.
For saving data my Angularjs code makes a $http call to my WebApi. I can return single data from my api fine but not sure whats the best method to return multiple values here. I can make it comma separated and then return as well, but wanted to know if there is a better approach to this.
So basically when the API saves data to my db, I want to return a variable, boolean value if the save was successful and an exception message in case the save was not successfully. Below is my code.
AngularJs Code:
service.saveData(data).then(function (res) {
//get someDataToReturn, dataSaved & exception raised if any from db save here.
}, function (err) {
});
WebApi Code:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
bool dataSaved = true;
string someDataToReturn = string.Empty;
//do some processing and updating someDataToReturn here
//Saving data to DB
dataSaved = SaveData(data);
//I want to return someDataToReturn, dataSaved(true or false) and exception raised from SaveData if any
return Ok(someDataToReturn);
}
//DB Call to save data
public bool SaveData(List<UserData> data)
{
try
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
return true;
}
catch (SqlException ex)
{
}
return false;
}
Let me know the best approach for this.
First of, you should check if the values in your request body is correctly populated.
Take a look at DataAnnotations.
You can use annotations to specify which properties in your model that are Required, Min and Maxlength etc.
Here's an example on how to define a Name property to be required on the UserData class
public class UserData
{
[Required]
public string Name { get; set; }
}
If the request model do not fulfill the specified rules set on the UserData class DataAnnotations, the context ModelState will be set to false and contain the DataAnnotations errors.
This can be used to determind if the current request is a bad request and return a proper http status code from that.
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //will return a 400 code
...
Then regarding the SaveData method. Capture the exception in the controller and return a proper status code from there
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //400 status code
try
{
SaveData(data);
}
catch(Exception e)
{
return InternalServerError(e); //500 status code
}
string someDataToReturn = string.Empty;
return Ok(someDataToReturn ); //200 status code
}
public void SaveData(List<UserData> data)
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
}
You can use the Controller class method Json(object data). Something like:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
return this.Json(SaveData(data));
}
See this.
you can create an entity and return it
public class BaseResult{
public bool Result{get;set;}
public string Errors{get;set;}
}
or only
return Ok( new { result = dataSaved , error= exception.Message});
the standard way is:
return 201 status code
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201
[HttpPost("data/save")]
public async Task<IHttpActionResult> SaveData([FromBody] List<UserData> data)
{
try
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// return response of 201 if you created the resource successfully
// typically return this with a uri to the new resource
return Created("location", saveData(data));
}
catch (Exception)
{
return InternalServerError();
}
}
I was wondering how I can achieve model validation with ASP.NET Web API. I have my model like so:
public class Enquiry
{
[Key]
public int EnquiryId { get; set; }
[Required]
public DateTime EnquiryDate { get; set; }
[Required]
public string CustomerAccountNumber { get; set; }
[Required]
public string ContactName { get; set; }
}
I then have a Post action in my API Controller:
public void Post(Enquiry enquiry)
{
enquiry.EnquiryDate = DateTime.Now;
context.DaybookEnquiries.Add(enquiry);
context.SaveChanges();
}
How do I add if(ModelState.IsValid) and then handle the error message to pass down to the user?
For separation of concern, I would suggest you use action filter for model validation, so you don't need to care much how to do validation in your api controller:
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http.Filters
{
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
Maybe not what you were looking for, but perhaps nice for someone to know:
If you are using .net Web Api 2 you could just do the following:
if (!ModelState.IsValid)
return BadRequest();
Depending on the model errors, you get this result:
{
Message: "The request is invalid."
ModelState: {
model.PropertyA: [
"The PropertyA field is required."
],
model.PropertyB: [
"The PropertyB field is required."
]
}
}
Like this, for example:
public HttpResponseMessage Post(Person person)
{
if (ModelState.IsValid)
{
PersonDB.Add(person);
return Request.CreateResponse(HttpStatusCode.Created, person);
}
else
{
// the code below should probably be refactored into a GetModelErrors
// method on your BaseApiController or something like that
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
}
}
This will return a response like this (assuming JSON, but same basic principle for XML):
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)
["A value is required.","The field First is required.","Some custom errorm essage."]
You can of course construct your error object/list any way you like, for example adding field names, field id's etc.
Even if it's a "one way" Ajax call like a POST of a new entity, you should still return something to the caller - something that indicates whether or not the request was successful. Imagine a site where your user will add some info about themselves via an AJAX POST request. What if the information they have tried to entered isn't valid - how will they know if their Save action was successful or not?
The best way to do this is using Good Old HTTP Status Codes like 200 OK and so on. That way your JavaScript can properly handle failures using the correct callbacks (error, success etc).
Here's a nice tutorial on a more advanced version of this method, using an ActionFilter and jQuery: http://asp.net/web-api/videos/getting-started/custom-validation
Or, if you are looking for simple collection of errors for your apps.. here is my implementation of this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
var errors = new List<string>();
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var response = new { errors = errors };
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
}
}
Error Message Response will look like:
{
"errors": [
"Please enter a valid phone number (7+ more digits)",
"Please enter a valid e-mail address"
]
}
You can use attributes from the System.ComponentModel.DataAnnotations namespace to set validation rules. Refer Model Validation - By Mike Wasson for details.
Also refer video ASP.NET Web API, Part 5: Custom Validation - Jon Galloway
Other References
Take a Walk on the Client Side with WebAPI and WebForms
How ASP.NET Web API binds HTTP messages to domain models, and how to work with media formats in Web API.
Dominick Baier - Securing ASP.NET Web APIs
Hooking AngularJS validation to ASP.NET Web API Validation
Displaying ModelState Errors with AngularJS in ASP.NET MVC
How to render errors to client? AngularJS/WebApi ModelState
Dependency-Injected Validation in Web API
Add below code in startup.cs file
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
{
ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
ErrorMessage = p.ErrorMessage,
ServerErrorMessage = string.Empty
})).ToList();
var result = new BaseResponse
{
Error = errors,
ResponseCode = (int)HttpStatusCode.BadRequest,
ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,
};
return new BadRequestObjectResult(result);
};
});
C#
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
...
[ValidateModel]
public HttpResponseMessage Post([FromBody]AnyModel model)
{
Javascript
$.ajax({
type: "POST",
url: "/api/xxxxx",
async: 'false',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
error: function (xhr, status, err) {
if (xhr.status == 400) {
DisplayModelStateErrors(xhr.responseJSON.ModelState);
}
},
....
function DisplayModelStateErrors(modelState) {
var message = "";
var propStrings = Object.keys(modelState);
$.each(propStrings, function (i, propString) {
var propErrors = modelState[propString];
$.each(propErrors, function (j, propError) {
message += propError;
});
message += "\n";
});
alert(message);
};
Here you can check to show the model state error one by one
public HttpResponseMessage CertificateUpload(employeeModel emp)
{
if (!ModelState.IsValid)
{
string errordetails = "";
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
string p = error.ErrorMessage;
errordetails = errordetails + error.ErrorMessage;
}
}
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("error", errordetails);
return Request.CreateResponse(HttpStatusCode.BadRequest, dict);
}
else
{
//do something
}
}
}
I had an issue implementing the accepted solution pattern where my ModelStateFilter would always return false (and subsequently a 400) for actionContext.ModelState.IsValid for certain model objects:
public class ModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
}
}
}
I only accept JSON, so I implemented a custom model binder class:
public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
if (address != null)
{
// moar val here
bindingContext.Model = address;
return true;
}
return false;
}
}
Which I register directly after my model via
config.BindParameter(typeof(AddressDTO), new AddressModelBinder());
You can also throw exceptions as documented here:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
Note, to do what that article suggests, remember to include System.Net.Http
Put this in the startup.cs file
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p =>p.ErrorMessage)).ToList();
var result = new Response
{
Succeeded = false,
ResponseMessage = string.Join(", ",errors)
};
return new BadRequestObjectResult(result);
};
});