POST controller value is always NULL - asp.net-mvc-4

Firstly thank you for taking the time out to read this post.
I've been building my first asp.net 4.5 MVC4 Web Api, but the value received in the controll POST method is always NULL.
To test the POST api method I've tried Fiddler and Google Chrome Simple REST Clinet but the result is always the same.
Here's my code:
Controller (POST)
// POST api/Terminal
public HttpResponseMessage PostTerminal(Terminal terminal)
{
if (terminal == null)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, terminal);
}
if (ModelState.IsValid)
{
db.Terminals.Add(terminal);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, terminal);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = terminal.Id }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
Model Class
public class Terminal
{
public int Id { get; set; }
[Required]
public int TerminalId { get; set; }
[Required]
public string Tag { get; set; }
[Required]
public DateTime TagDate { get; set; }
}
In the above code, the value received in the POST method terminal is always NULL.
Here's the command I've been using to POST using Fiddler:
Method:
POST
Address:
http://localhost:52036/api/terminal
Header:
Content-Type: application/json
Host: localhost:52036
Content-Length: 60
Body:
{"TerminalId":123,"Tag":"222","TagDate":2014-04-13 04:22:12}
----------- EDIT - 13-04-2014 14:56 -------------
I have modified my Fiddler http post request as advised to in some of the replies to this question with the following details. However I keep getting a 500 HTTP error and my API code doesn't even reach the Controller when testing in VS2013 debug mode.
Address
POST
http://localhost:52036/api/terminal/
Header
Content-Type: application/json; charset=utf-8
Host: localhost:52036
Content-Length: 62
Body
{"TerminalId":123,"Tag":"222","TagDate":"2014-04-13 04:22:12"}
-------------- Edit 13/04/2014 20:38 -----------------
Ok, using Google's Chrome REST Client, I've noticed the following return error message:
{"Message":"An error has occurred.","ExceptionMessage":"Property
'TerminalId' on type 'ClockingDemo.Models.Terminal' is invalid.
Value-typed properties marked as [Required] must also be marked with
[DataMember(IsRequired=true)] to be recognized as required. Consider
attributing the declaring type with [DataContract] and the property
with
[DataMember(IsRequired=true)].","ExceptionType":"System.InvalidOperationException","StackTrace":"
at
System.Web.Http.Validation.Validators.ErrorModelValidator.Validate(ModelMetadata
metadata, Object container)\r\n at
System.Web.Http.Validation.DefaultBodyModelValidator.ShallowValidate(ModelMetadata
metadata, ValidationContext validationContext, Object container)\r\n
at
System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata
metadata, ValidationContext validationContext, Object container)\r\n
at
System.Web.Http.Validation.DefaultBodyModelValidator.ValidateProperties(ModelMetadata
metadata, ValidationContext validationContext)\r\n at
System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata
metadata, ValidationContext validationContext, Object container)\r\n
at
System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object
model, Type type, ModelMetadataProvider metadataProvider,
HttpActionContext actionContext, String keyPrefix)\r\n at
System.Web.Http.ModelBinding.FormatterParameterBinding.<>c__DisplayClass1.b__0(Object
model)\r\n at
System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass361.<>c__DisplayClass38.<Then>b__35()\r\n
at
System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass49.<ToAsyncVoidTask>b__48()\r\n
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func1
func, CancellationToken cancellationToken)"}
I think the problem is coming from the fact that my INTEGER property TerminalId is defined as [Required]. I have a feeling there might be an issue with setting a non-NULLABLE property and [Required].
Very much open to suggestions.
----------------- ANSWER ---------------
I finally stumbled across this thread which solved the problem for me.
DataAnnotation for Required property
Simply paste this into the Global.asax file:
GlobalConfiguration.Configuration.Services.RemoveAll(
typeof(System.Web.Http.Validation.ModelValidatorProvider),
v => v is InvalidModelValidatorProvider);
I'm still open to other solutions if you believe there is a better method.

In Fiddler you have to use like below
Your Request Body should like this {"TerminalId":123,"Tag":"222","TagDate":"2014-04-13 04:22:12"}
and in your request header you need to pass content type like this Content-Type: application/json; charset=utf-8

In my case I have a hybrid ASP.NET Web Forms with MVC and Web API.
Just solved like this:
public HttpResponseMessage PostTerminal(JObject jsonMessage)
{
var terminal = jsonMessage.ToObject<Terminal>();
if (terminal == null)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, terminal);
}
...
}
References: http://weblog.west-wind.com/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API-Controller-Methods

Related

Model Binding for multipart/form-data (File + JSON) post in ASP.NET Core 1.1

I'm attempting to build an ASP.NET Core 1.1 Controller method to handle an HTTP Request that looks like the following:
POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474
----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain
<< Contents of my file >>
----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json
{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
It's a multipart/form-data request with one part being a (small) file and the other part a json blob that is based on a provided specification.
Ideally, I'd love my controller method to look like:
[HttpPost]
public async Task Post(UploadPayload payload)
{
// TODO
}
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
But alas, that doesn't Just Work {TM}. When I have it like this, the IFormFile does get populated, but the json string doesn't get deserialized to the other properties.
I've also tried adding a Text property to UploadPayload that has all the properties other than the IFormFile and that also doesn't receive the data. E.g.
public class UploadPayload
{
public IFormFile File { get; set; }
public UploadPayloadMetadata Text { get; set; }
}
public class UploadPayloadMetadata
{
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
A workaround that I have is to avoid model binding and use MultipartReader along the lines of:
[HttpPost]
public async Task Post()
{
...
var reader = new MultipartReader(Request.GetMultipartBoundary(), HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
var filePart = section.AsFileSection();
// Do stuff & things with the file
section = await reader.ReadNextSectionAsync();
var jsonPart = section.AsFormDataSection();
var jsonString = await jsonPart.GetValueAsync();
// Use $JsonLibrary to manually deserailize into the model
// Do stuff & things with the metadata
...
}
Doing the above bypasses model validation features, etc. Also, I thought maybe I could take that jsonString and then somehow get it into a state that I could then call await TryUpdateModelAsync(payloadModel, ...) but couldn't figure out how to get there either - and that didn't seem all that clean either.
Is it possible to get to my desired state of "transparent" model binding like my first attempt? If so, how would one get to that?
The first problem here is that the data needs to be sent from the client in a slightly different format. Each property in your UploadPayload class needs to be sent in its own form part:
const formData = new FormData();
formData.append(`file`, file);
formData.append('md5', JSON.stringify(md5));
formData.append('sessionIds', JSON.stringify(sessionIds));
Once you do this, you can add the [FromForm] attribute to the MD5 property to bind it, since it is a simple string value. This will not work for the SessionIds property though since it is a complex object.
Binding complex JSON from the form data can be accomplished using a custom model binder:
public class FormDataJsonBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Fetch the value of the argument by name and set it to the model state
string fieldName = bindingContext.FieldName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
if(valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);
// Do nothing if the value is null or empty
string value = valueProviderResult.FirstValue;
if(string.IsNullOrEmpty(value)) return Task.CompletedTask;
try
{
// Deserialize the provided value and set the binding result
object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
catch(JsonException)
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
You can then use the ModelBinder attribute in your DTO class to indicate that this binder should be used to bind the MyJson property:
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
[FromForm]
public string Md5 { get; set; }
[ModelBinder(BinderType = typeof(FormDataJsonBinder))]
public List<string> SessionIds { get; set; }
}
You can read more about custom model binding in the ASP.NET Core documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding
I'm not 100% clear on how this would work for ASP.NET Core but for Web API (so I assume a similar path exists here) you'd want to go down the road of a Media Formatter. Here's an example (fairly similar to your question) Github Sample with blog post
Custom formatters might be the ticket? https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters

How to work with [Required] attribute & Model State Validation within Web Api Put

Currently I am facing, a problem, when try to call Web Api put method from MVC Api Client, lets describe my code structure bellow
Test Model (Web Api end)
public sealed class Test
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
Web Api PUT Method
public HttpResponseMessage Put(string token, IEnumerable<Test> data)
{
[...]
return Request.CreateResponse(HttpStatusCode.OK);
}
Web Api Custom Filter
public sealed class ValidateFilterAttribute : ActionFilterAttribute
{
/// <summary>
///
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Call from Web Api Client
async System.Threading.Tasks.Task VerifiedFAccount()
{
using (var client = GetHttpClient())
{
var url = string.Concat("/api/Verfication", "?token=", token);
var data = new SampleTest { Id = 1, Name = "xxx" };
var temp = new List<SampleTest>();
temp.Add(data);
using (HttpResponseMessage response = await client.PutAsJsonAsync
<IEnumerable<SampleTest>>(url, temp).ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
}
}
}
Client code unable to execute Api call (Even I placed the debug point within Web Api Put method, unable to hit the debug point) & always got the bellow error response
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?STpcRGV2QXJlYUxvY2FsXENPTVBBTlkgLSBQU1AgUFJPSkVDVFNcRS1BdXRob3JpdHkgLSBBdXN0cmVsaXlhXFNvdXJjZUNvbnRyb2xcVHJ1bmtcMDYgRGVjIDIwMTNcRS1BdXRob3JpdHkuQXBpIC0gMjAxM1xFYXV0aG9yaXR5LldlYi5BcGkuUHJlc2VudGF0aW9uTGF5ZXJcYXBpXFNtc2ZBY2NvdW50VmVyZmljYXRpb24=?=
Cache-Control: no-cache
Date: Mon, 14 Apr 2014 11:23:27 GMT
Server: Microsoft-IIS/8.0
Content-Length: 2179
Content-Type: application/json; charset=utf-8
Expires: -1
}}
But when I remove [Required] from Test Model (Web Api end). Then above described client code execute successfully.
Please tell me what is the reason of this kind of confusing behavior ?
The issue you are facing might be because of the behaviour of the default configuration when it comes to data validation. You have a Required attributed on a non-nullable type and since int can't be null, it will always have a value (the default of 0) if the incoming request does not provide the value.
In these cases, the model validator will throw an exception because it doesn't make sense to have a Required attribute on a property that can't be null.
The straightforward way you would be to change a setting on your MVC application:
DataAnnotationsModelValidatorProvider
.AddImplicitRequiredAttributeForValueTypes = false;
This will get rid of the error that is thrown by the framework. This introduce the problem that you will get a value of 0 even when the request does not include the property. It makes more sense to have your integer be of Nullable<int>. The Required attribute will be able to handle a null value and you will know whether or not the incoming request included the property
public sealed class Test
{
[Required]
public int? Id { get; set; }
[Required]
public string Name { get; set; }
}

Fiddler - Change HTTP Request Header

I wish to create a HTTP request header using Fiddler.
I have a Service running which exposes a Method, which has an object parameter. The object looks like this:
[DataMember]
public string Name { get; set; }
[DataMember]
public string Data { get; set; }
[DataMember]
public string Details { get; set; }
....
Does anyone know how I can populate these objects and send them to my WCFServices? I am using localhost.
Solved by:
Opening Fiddler, clicking on Composer, adding:
Content-Type: application/json; charset=utf-8 In the Parsed tab.
And Key pair values in the Request Body, e.g:
{
"Name" : "Arnold",
"Data" : "SomeDataHere"
}
how I can populate these objects
Take a look at SOAP UI or at the WCF Test Client.

ASP.Net Web Api not binding model on POST

I'm trying to POST JSON data to a Web Api method but the JSON data is not binding to the model.
Here's my model:
[DataContract]
public class RegisterDataModel
{
[DataMember(IsRequired = true)]
public String SiteKey { get; set; }
[DataMember(IsRequired = true)]
public String UserId { get; set; }
[DataMember(IsRequired = true)]
public String UserName { get; set; }
}
Here's my Web Api action:
public class RegisterController : ApiController
{
public Guid Post([ModelBinder] RegisterDataModel registerDataModel)
{
if (!ModelState.IsValid)
{
throw new ModelStateApiException(ModelState);
}
var userProfileDataContract = userProfileBusinessLibrary.GetNewOne();
userProfileDataContract.UserId = registerDataModel.UserId;
userProfileDataContract.UserName = registerDataModel.UserName;
var userKey = userProfileBusinessLibrary.Register(registerDataModel.SiteKey, userProfileDataContract);
return userKey;
}
}
Before I added [ModelBinder], registerDataModel was null. After adding [ModelBinder], registerDataModel is a RegisterDataModel instance, but all of the property values are null.
Here's my Request via Fiddler:
http://local.testwebsite.com/api/register
Request Headers:
User-Agent: Fiddler
Host: local.testwebsite.com
Content-Length: 89
Content-Type: application/json; charset=utf-8:
Request Body:
{
"SiteKey":"qwerty",
"UserId": "12345qwerty",
"UserName":"john q"
}
What am I missing to make my post data bind to the RegisterDataModel properties? Thanks for your help.
Not related to the OP's problem, but the title of the question
led me here when I used (public) fields instead of properties
in the Model class (i.e. no {get; set;}).
It turned out that this also causes the binding to fail.
Maybe helps someone.
How are you creating the JSON request? Through Fiddler request builder? Try just the following in the request body.
{
"SiteKey":"qwerty",
"UserId": "12345qwerty",
"UserName":"john q"
}
I'm guessing 'Request Body:' is also part of your request body. Remove that and check.
In my case, app's requests are passed through a middleware called "API Manager" for authentication / authorization before forwarding to my .NET Web API. POST parameter isn't binded because, for some reason I'm no idea why, the "Content-Length" is emitted from the Headers.The reason is because, the default JsonMediaTypeFormatter always check requests' Content-Length before doing model binding, and if the Content-Length is not presented it will set the parameter to NULL.

WebApi Put "The parameters dictionary contains a null entry for parameter..."

public class ContactsController : ApiController
{
private static readonly List<Contact> _contacts = new List<Contact>();
public Contact PutContacts(int id, Contact contact)
{
if (_contacts.Any(c => c.Id == contact.Id) == false)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
contact.LastModified = DateTime.Now;
return contact;
}
}
http put header:
PUT /api/contacts/3 HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:8080
body:
{"Id":3,"Name":"mmm","Phone":"000 000 0000","Email":"mmm#gmail.com","LastModified":"2012-03-08T23:42:13.8681395+08:00"}
response:
HTTP/1.1 400 Bad Request
"The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'SelfHost.Contact PutContacts(Int32, SelfHost.Contact)' in 'SelfHost.ContactsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
Why? thanks.
PS:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I am having this same issue. The query parameter, "id" is null and the object "contact" is filled in. I think this is a model binding issue. If you put a break point in the controller, look at this expression:
this.ControllerContext.RouteData.Values["id"]
You will see that the value is there in the route data; its just not getting set on the model. I have an ActionFilter and if I put a break point in there as well, I see that the actionContext's ActionArgument has a key for it, but a null value.
Im still researching...
I am unable to reproduce the issue you are describing.
Model:
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public DateTime LastModified { get; set; }
}
Controller:
public class ContactsController : ApiController
{
public Contact Put(int id, Contact contact)
{
return contact;
}
}
Client:
class Program
{
static void Main()
{
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var data = Encoding.UTF8.GetBytes(#"{""Id"":3,""Name"":""mmm"",""Phone"":""000 000 0000"",""Email"":""mmm#gmail.com"",""LastModified"":""2012-03-08T23:42:13.8681395+08:00""}");
var result = client.UploadData("http://localhost:1405/api/contacts/4", "PUT", data);
Console.WriteLine(Encoding.UTF8.GetString(result));
}
}
}
When I run the client, the following request is being sent:
PUT /api/contacts/4 HTTP/1.1
Content-Type: application/json
Host: localhost:1405
Content-Length: 119
Expect: 100-continue
Connection: Keep-Alive
{"Id":3,"Name":"mmm","Phone":"000 000 0000","Email":"mmm#gmail.com","LastModified":"2012-03-08T23:42:13.8681395+08:00"}
and I get the correct result from the server. So I guess that the request you are showing is not the actual request that's being sent to the server.
craigtadlock, thanks for your help and sending the bug over to Microsoft. I was having the same problem with PUTs. None of them worked. As a workaround, I hacked POST to handle PUT. If the object I'm posting to the server already has it's ID, the POST code treats it as a PUT with that ID. Pseudo code is:
public HttpResponseMessage<MyClass> PostMyClass(MyClass myObject)
{
if( myObject.ID != 0 ){
//do PUT code
}else{
//do POST code
}
}