Posting to MVC Controller, ViewModel stays null - asp.net

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

Related

POST gets null value

I don't know the exact format of the incoming JSON and have no control of it. I eventually need an object to match the incoming JSON format.
My thought was to treat it as a string and write it to a file, whatever format it is, so I can examine it and create an object to match.
From Fiddler, I'm sending this:
URL: https://localhost:44351/api/values
Headers
User-Agent: Fiddler
Host: localhost:44351
Content-Length: 13
Content-Type: application/json
Body
{name='test'}
'Get' works and returns properly. 'Post' gets called but when I debug it, it gets a null value for the string.
It creates the file but, understandably, it is empty.
public class ValuesController: ApiController
{
public string Get(int id)
{
return "value=" + id.ToString();
}
public IHttpActionResult Post([FromBody] string value)
{
File.WriteAllText(#"C:\temp\Import.txt", value);
return Ok();
}
or
public IHttpActionResult Post([FromBody] GenericText value)
{
File.WriteAllText(#"C:\temp\Import.txt", value.Name);
return Ok();
}
public class GenericText
{
public string Name { get; set; }
}
Routing
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
How do I get access to the incoming JSON so I can write it out to the file?
You are posting a json object: {name='test'} and expect the body to be of type string in the controller. There are two possible solutions to your problem:
post a string and keep expecting a string in the controller.
keep posting the json object with the property name of type string, and change the expected type in the controller.
which would look like:
public class MyRequestObject
{
public string Name {get;set;}
}
public IHttpActionResult Post([FromBody] MyRequestObject value)
{
File.WriteAllText(#"C:\temp\Import.txt", value.Name);
return Ok();
}
I never did solve this directly, but was able to work around it.
If your controller descends from Controller, you get access to Request
//
// Summary:
// Gets the HttpRequestBase object for the current HTTP request.
//
// Returns:
// The request object.
public HttpRequestBase Request { get; }
I used it like so
value = new StreamReader(Request.InputStream).ReadToEnd();
Note that in some cases you need to reset the input stream to the beginning to be able to read any content, but I didn't need that step in the final solution.

How to consume ItemService of Sitecore Services Client

There are few tabs and a content panel. End user (EU) will click on a tab and the panel's content changes asynchronously.
eg:
<ul class="products">
<li class="p1">product1</li>
<l1>product2</li>
</ul>
<div class="product-data">
NAME : <span> <product name here> </span>
COLOR : <span> <product color here> </span>
</div>
<script>
(function ($) {
$(".p1").click(function(){
HelloWorld();
});
function HelloWorld() {
$.ajax({
type: "POST",
url: 'http://mysite/Services/myService.asmx/HelloWorld',
data: "pid:" + someId,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
alert(response.d);
},
failure: function (response) {
alert("ERR-" + response.d);
}
});
}
})(jQuery);
</script>
Each product is an item in the content tree. When EU clicks on a tab, an ajax call is made, which will get that product's data and bind to the panel.
I'm trying to achieve this functionality using a web service and return JSON to front end:
[WebMethod]
public string HelloWorld(string pid)
{
//get a certain product details and return it as JSON
Sitecore.Data.Items.Item newItem = Sitecore.Context.Item;
if (newItem != null)
return newItem.Name;
else
return "it was null";
}
Of course, the result is "it was null" as I understand this has to be done with Item Service of SSC, but unable to find any suitable/beginner example.
Using sitecore 8 with ASP.NET
You will want to use Entity Service instead of Item Service of Sitecore.Services.Client. It will let you serve custom models that are specific to the type of data you want to display for each product.
First, you will need to create a class to represent your Product. It needs to implement Sitecore.Services.Core.Model.EntityIdentity.
Just an FYI if you are using Sitecore SPEAK ensure you define a property named itemId with that casing, SPEAK requires it.
public class ProductModel : Sitecore.Services.Core.Model.EntityIdentity
{
public string itemId { get; set; }
public string ProductName { get; set; }
...
}
When Developing with Sitecore.Services.Client you'll want to follow Sitecore best practices. A simple Controller passing off all the computation to a Repository for the type of Model. In this case a ProductRepository.
The controller needs to implement EntityService with the type of Model.
[ValidateAntiForgeryToken]
[ServicesController]
public class ProductController : EntityService<ProductModel>
{
public ProductController(IRepository<ProductModel> repository)
: base(repository)
{
}
public ProductController()
: this(new SitecoreItemRepository())
{
}
}
This controller exposes the methods of the Repository Get, GetById, Add, etc.
public class ProductRepository : Sitecore.Services.Core.IRepository<ProductModel>
{
public ProductModel FindById(string id)
{
// code to find by id
}
}
See here for a full example of Entity Service and here

How do you require certain properties from the URI of the request?

I have a very simple model:
public class FoobarGETRequestModel {
[Required]
public string Id { get; set; }
}
It's being used for binding like so:
[HttpGet]
[ValidateModel] //This is a custom attribute for handling validation errors
public async Task<HttpResponseMessage> Foobar([FromUri]FoobarGETRequestModel model) {
...
}
If I make a request to /api/controller/foobar (notice I'm not providing an Id parameter), the ModelState.IsValid property always returns true (from both an action filter and from within the method above).
How do I bind via the URI and still leverage the frameworks ModelState validation?
Edit: Here is what ValidateModel looks like:
if (actionContext.ModelState.IsValid == false) {
var responseObject = new FooApiExceptionResponse() {
Errors = actionContext.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
};
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, responseObject);
}
Did you check your RouteTable? If you are telling routetable that your "id" parameter is optional in default route, this could occur.

Json.Net Deserialization - Web API and Nullable Date

Say you've got a model that looks like
public class UserModel
{
public string UserName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The DateOfBirth field isn't required, but could be specified. You have a Web API POST endpoint that looks like
[Route("")]
[AcceptVerbs("POST")]
public async Task<IHttpActionResult> Create(UserModel user)
{
}
And we've set the JSON serializer in start up like so,
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new IsoDateTimeConverter());
settings.Error += (sender, args) => Console.WriteLine("This event is fired ok");
}
If we send some JSON to the endpoint that looks like this
{
"userName": "User1",
"dateOfBirth": "jhdgjhjfg"
}
...the error event is fired in the Serializer settings and the endpoint is called. At this point, the DateOfBirth field is null and I don't have any context that a deserialization error has occurred
Reading the JSON.Net documentation, because Handled == false in the Error event arguments of the Settings object, an exception should be raised into the application code - this doesn't happen? Is there a setting I haven't configured correctly for this?
How can I get context within the action so that I know a value was specified for a field and couldn't be deserialized? Even global behaviour would be fine, as long as I know this has happened and can return a 400.
UPDATE:
We can use a filter to check the Model state, then check the Model State errors for exceptions of type JsonReaderException. This lets you return a 400 with a list of violating fields
public class CheckJsonExceptionModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
var fieldsInError = new List<string>();
foreach (var jsonException in
actionContext.ModelState.Keys.SelectMany(key => actionContext.ModelState[key].Errors)
.Select(error => error.Exception).OfType<JsonReaderException>())
{
Trace.TraceError(jsonException.Message);
fieldsInError.Add(jsonException.Path);
}
var apiError = new { ErrorMessages.BadRequestModel.Message, FieldsInError = new List<string>() };
foreach (var fieldError in fieldsInError)
{
apiError.FieldsInError.Add(fieldError);
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
You have multiple options. But first, you are getting no exception because the WebApi handles this exception. Bad news.
Good news, you can handle it in at least two ways; use the ModelState.IsValid property - in your case it will be false. You can access them in your post-method. When you remove the invalid dateOfBirth it is true ;-)
Or you can use an ActionFilterAttribute to put it on your methods for re-use purposes.
For example:
public async Task<IHttpActionResult> Create(UserModel user) {
if (!ModelState.IsValid) {
// ModelState.Keys // Get all error-keys
}
}

Web API Model properties are null

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.

Resources