Web API Model properties are null - asp.net

My Controller is able to create the model object but all the properties related to model and assigned to null values
Environment : VS 2010, ASP.NET MVC RC latest, jQuery 1.7.1
Following is the Web API Controller code
public class Customer
{
public string Name { get; set; }
public string City { get; set; }
}
public class UserController : ApiController
{
public Customer Post(Customer user)
{
return user;
}
}
Following is the ajax calling code
$.ajax('/api/user',
{
ContentType: "application/x-www-form-urlencoded; charset=UTF-8",
dataType: 'json',
type: 'POST',
data: JSON.stringify({ "Name": "Scott", "City": "SC" })
});
Controller does create the model "Customer" object but both "Name" and "City" properties are null.
What's wrong here?
I have read many similar issues on this site but could not find the solution.

This blog here gives a good idea about how Model Binding differs in ASP.NET Web project and a ASP.NET Web API project.
I was facing a similar issue in the project I was working on and adding a explicit ModelBinding attribute made the property values stick
Requested Data :
var customer = { Name : "customer Name", City : "custome City" }
$.ajax({
url : ...
type: ...
data : customer
});
Request Class:
public class Customer
{
public string Name { get; set; }
public string City { get; set; }
}
Controller :
public class UserController : ApiController
{
[HttpGet]
public Customer Get([ModelBinder] Customer user)
{
// fetch from whereever
}
}

I'm going through the same issue right now. I'm not 100% sure of the answer, but below is my javascript and I've added to the class [DataModel] and to the Properties [DataMember]. That is:
[DataModel]
public class Customer
{
[DataMember] public string Name { get; set; }
[DataMember] public string City { get; set; }
}
And My JavaScript
$(document).ready(function () {
// Send an AJAX request
$.getJSON("api/session/GetAll",
function (data) {
// On success, 'data' contains a list of products.
$.each(data, function (key, val) {
//debugger;
// Format the text to display.
//var str = val.Name + ': $' + val.Price;
var str = 'abcd';
// Add a list item for the product.
$('<li/>', { text: str })
.appendTo($('#products'));
});
});
});

Faced a similar issue and my problem turned out to be invalid JSON in the body of the request. There was a comma missing after one of the fields. So it seems like the default model binder just binds null if there are any syntax errors in the JSON.

Next time this happens, ensure that there is no "internal" keyword on the property setter. That is:
instead of public string Comment {get; internal set;}
use public string Comment {get; set;}
This fixed it for me.

Related

Swagger UI doesn't render body parameter field for my complex type parameter in GET action of my Controller

I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.

Model Validation With Web API and JSON Patch Document

I'm using JsonPatchDocument with ASP.NET 4.5 and Web Api. My controller looks like this:
[HttpPatch]
[Route("MyRoute/{PersonItem1}/{PersonItem2}/")]
public IHttpActionResult ChangePerson([FromHeader]Headers, [FromBody]JsonPatchDocument<PersonDto> person)
{
// Do some stuff with "person"
}
And PersonDto:
public class PersonDto
{
public string Name { get; set; }
public string Email { get; set; }
}
Now, I may send a PATCH request that is something like:
{
"op": "op": "replace", "path": "/email", "value": "new.email#example.org"
}
Now let's say I add some data annotations:
public class PersonDto
{
public string Name { get; set; }
[MaxLength(30)]
public string Email { get; set; }
}
What is the best way to ensure this validation is honored without writing additional validation. Is it even possible?
There is the simple method:
Get your object from your repository.
Deep copy the object so you have object A and B.
Apply the change with person.ApplyUpdatesTo(objB).
Create an extension method to validate the difference between object A and B.
If the validation is good proceede, if not throw an error.
This would catch if the client was attempting to modify immutable fields or if the new information in object B violates your constraints.
Note that this is not a great solution in that you would have to change your code in two places if you happen to change your constraints.

Posting to MVC Controller, ViewModel stays null

I have this ASP.NET MVC controller action and viewmodel:
public JsonResult Upload(UploadModel MyModel)
{
//do stuff with MyModel
}
public class UploadModel
{
public string Name;
}
And in Angular, a method for submitting a form to this action:
function onSubmit() {
console.log(vm.model);
$http.post('/Home/Upload', vm.model).
then(function (response)
{
// do stuff with response
});
};
When I log vm.model, it looks like this:
{ Name : "Some cool name" }
It looks exactly the same in the request payload. But when I post this object to the Upload method, Name appears to be null. However, when I change the Upload method to accept just a string instead of a viewmodel:
public JsonResult Upload(String Name)
{
//do stuff with Name
}
And post the exact same object to it, it does get recognized.
What is going on? Why isn't MVC recognizing my JS object as a viewmodel?
Name is a field, not a property (no getter/setter) so the DefaultModelBinder cannot set the value. Change it to
public class UploadModel
{
public string Name { get; set; }
}
You should serialize your form data and then post
var cpformdata = $("#Myform form").serializeArray();
$.ajax({
url: url,
type: 'POST',
data: cpformdata,
success: function (data) {
}

POST Method fails to populate request object in ServiceStack

I've been using service stack for a while and came upon a scenario where the POST method uses the default instance of the IReturn object (with all the properties defaulting to their datatype values). The values supplied as part of the Route (/product/1234345/) are the only ones populated. I've laid out an example below:
[Route("/search/{searchMethod}/books")]
public class SearchRequest : IReturn<SearchResponse>
{
public SearchProvider searchProvider { get; set; }
public string searchTerm { get; set; }
public string categoryID { get; set; }
public long maxResults { get; set; }
//Only this property gets populated if method is post
public string searchMethod { get; set; }
}
public SearchResponse Any(SearchRequest searchRequest)
{
//This works only for non-post requests
return Put(searchRequest);
}
public SearchResponse Get(SearchRequest searchRequest)
{
//This works
return Put(searchRequest);
}
public SearchResponse Post(SearchRequest searchRequest)
{
//This does not
return Put(searchRequest);
}
public SearchResponse Put(SearchRequest searchRequest)
{
//Code for put method goes here
}
I'm then using a client to call these methods
SearchServiceClient searchClient = new SearchServiceClient(SearchServiceAPIUrl);
SearchResponse searchResponse = searchClient.Search(SearchProvider.SampleSearchProvider, searchterm, categoryID, 100,"conservative");
Any help is really appreciated
Thanks
I've always just populated my request object in the constructor and sent it to the service
searchClient.Post(new SearchRequest(SearchProvider.SampleSearchProvider,
searchterm, categoryID, 100,"conservative")):
I finally found the solution after tinkering with the DTO. It seems for post requests all DTO properties needed to have a [DataMember] attribute for serialization/deserialization and make sure that the class also has a [DataContract] attribute.

WebAPI OData and customizing navigation links

I'm working on building a WebAPI based OData service and I’m having issues with navigation links. Basically I have two classes where one has a reference to another. When I request either atom or verbose JSON I can see that I have a link between the two. However, I’d like to customize the uri to have it point to a different location rather than the default assumed by the OData library.
Using a simple example, assume that I have two entity sets called ‘entity1’ and ‘entity2’. These are exposed as OData services located at: /api/entities1 and /api/entities2 respectively.
Here’s my sample model code:
public class Entity1 {
public int ID { get; set; }
public string Name { get; set; }
public virtual Entity2 OtherEntity { get; set; }
}
public class Entity2 {
public int ID { get; set; }
public string Value { get; set; }
}
I’m using the ODataConventionModelBuilder to register these as follows:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Entity1>("entities1");
builder.EntitySet<Entity2>("entities2");
IEdmModel model = builder.GetEdmModel();
config.Routes.MapODataRoute(routeName: "OData", routePrefix: "api", model: model);
I've implemented the controller as an EntitySetController. All of this works as expected and I get the following response when I request verbose JSON:
{
"d": {
"results": [{
"__metadata": {
"id": "http://localhost:37826/api/entities1(1)",
"uri": "http://localhost:37826/api/entities1(1)",
"type": "ODataSample.Models.Entity1"
},
"OtherEntity": {
"__deferred": {
"uri": "http://localhost:37826/api/entities1(1)/OtherEntity"
}
},
"ID": 1,
"Name": "First Entity"
}]
}
}
What I’d like to do is to have the ‘OtherEntity’ field in an Entity1 instance refer to the associated Entity2 instance under /api/entities2 so that the link appears something like /api/entities2(2) (assuming the ID of the Entity2 instance is '2').
I know that I could just make the type of ‘OtherEntity’ a Uri and insert the appropriate value in my controller but that seems a bit of a hack (but I could be wrong). From what understand about OData, I believe the right way to do this is to modify the navigation property but I’m not sure how or where.
Any help is appreciated. Thanks in advance.
Cheers,
Steve
You could do something like the following:
var entities1 = builder.EntitySet<Entity1>("Entities1");
entities1.HasNavigationPropertyLink(entities1.EntityType.NavigationProperties.First(np => np.Name == "OtherEntity"),
(context, navigation) =>
{
return new Uri(context.Url.ODataLink(new EntitySetPathSegment("Entities2"), new KeyValuePathSegment(context.EntityInstance.OtherEntity.Id.ToString())));
}, followsConventions: false);

Resources