How to pass a ko.observablearray via JSON to an MVC controller - asp.net-mvc

I'm using Knockout JS to build a model to pass to an MVC controller. The ko.observable() items are passed to the controller no problem, however, the ko.observableArray([]) data is appearing as "count=0" at the controller.
Below is the object I am building in my View:
var AddViewModel = function () {
self.ModelRequest = {
Object: {
VarArray: ko.observableArray([]),
Var1: ko.observable(""),
Var2: ko.observable("")
}
};
....
The ModelRequest.Object.VarArray is an ko.observableArray contains a few attributes in the object: Name, Id, Code, Type.
Below is how I'm sending the data via JSON:
p = ko.toJSON(AddViewModel.ModelRequest);
debugger;
$.ajax({
url: url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(AddViewModel.ModelRequest),
success: function (data) {
...something...
}
});
When I am debugging the code, I examine the p variable described above and I see the below:
{"Object":{"VarArray":[{"Name":"Name 1", "Id":2, "Code":"50.1", "Type":"A"}],
"Var1":"abc", "Var2":"def"}}
When I examine the object being passed into the controller, Var1 and Var2 have the correct values, however, the VarArray is "Count=0".
Any thoughts? Thanks for taking the time to look at this. I'll try any ideas at this point!!
EDIT 10/6/13:
This is my controller action:
[HttpPost]
public CRUDResponse AddItem(AddRequest ModelRequest)
{
... something here ...
}
At this point when I examine the ModelRequest I see that VarArray is "Count = 0".
Edit 10/8/13:
This is the details of the AddRequest:
#region Public Members
public ObjectType Object { get; set; }
#endregion Public Members
Where the ObjectType is:
#region Public Members
public int Var1 { get; set; }
public int Var2 { get; set; }
public List<SpecType> VarArray { get; set; }
#endregion Public Members
Where the SpecType is
public string Name { get; set; }
public int Id { get; set; }
public string Code { get; set; }
public FieldType Type { get; protected set; }
And the FieldType is a Enum.
UPDATE: I had just found the problem. It looks like the property is not getting serialized properly through JSON when I make a call to my Web API from the UI. The above-mentioned property is of type TypaA which inherits from TypeB. TypeB contains all of the fields needed by TypeA. When I change the property failing to serialize to be of type TypeB, instead of TypeA, it serializes just fine and I get all of the values I need reflected in Web API.
So, basically, JSON fails to serialize a value if it's type is derived from another type. Removing the inheritance by declaring a value to be of base type fixes the issue.
So, is there a way to serialize a property whose type inherits from another class?
Eric

I think the problem is that either A: you are never populating the observableArray, or B: you are not receiving the proper object type back on the controller, either because you are sending it incorrectly or receiving it improperly.
Try doing this -
function testData(name) {
var self = this;
self.Name = ko.observable(name);
}
inside of your view model
var AddViewModel = function () {
self.ModelRequest = {
Object: {
varArray: ko.observableArray([
new testData('Your my boy blue'),
new testData('Frank the tank')
]),
var1: ko.observable(""),
var2: ko.observable("")
}
};
}
And see if your controller action is actually getting your data back.
If not then you are most likely not matching the object you are sending to the controller with an object the controller recognizes.

Related

Json and ASP.NET MVC Model Binding

I have a UI that looks like this:
I am trying to data of the newly created row to the server so that the server may save it.
I am sending data in JSON format from the client to my MVC application. Here's my ajax request:
var values = []; // an array with each item being an object/associative array
// more code to get values into variables
for (var i = 0; i < cultures.length; i++) {
var cultureName = cultures[i];
var valueTextBox = $(row).find(...);
var value = $(valueTextBox).val();
var cultureNameAndValue = { 'CultureShortName' : cultureName, 'StringValue' : value };
values.push(cultureNameAndValue);
}
var stringTableRow =
{
'ResourceKeyId': resourceKeyId,
'Key': resourceKeyName,
'CategoryId': categoryId,
'CategoryName': categoryName,
'StringValues': values
};
var stringified = JSON.stringify({ StringTableRow: stringTableRow });
$.ajax('/Strings/JsonCreateNew',
{
cache: false,
async: false,
type: 'POST',
contentType: 'application/json; charset=UTF-8',
data: stringified,
dataType: 'json',
error: SaveNewResourceClientSideHandler.OnError,
success: SaveNewResourceClientSideHandler.OnSuccess
});
Here's the data it sends (as seen in Firebug):
{"StringTableRow":{"ResourceKeyId":"","Key":"Foo",
"CategoryId":"1","CategoryName":"JavaScript",
"StringValues":[
{"CultureShortName":"en-US","StringValue":"Something"},
{"CultureShortName":"fr-FR","StringValue":""}]
}}
Here's my server side code:
public ActionResult JsonCreateNew(StringTableRow row)
{
// CreateNewStringTableRow(row);
// return a success code, new resource key id, new category id
// return Json(
new { Success = true, ResourceKeyId = row.ResourceKeyId,
CategoryId = row.CategoryId },
JsonRequestBehavior.AllowGet);
return new EmptyResult();
}
And here's the business object that I want my incoming POST'ed data to be bound to:
public class StringTableRow
{
public StringTableRow()
{
StringValues = new List<CultureNameAndStringValue>();
}
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public string CategoryName { get; set; }
public IList<CultureNameAndStringValue> StringValues { get; set; }
}
public class CultureNameAndStringValue
{
public string CultureShortName { get; set; }
public string StringValue { get; set; }
}
Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
}
}
Problem:
The action JsonCreateNew receives an object that is not null but has all properties uninitialized, i.e all nullable properties are null and value properties are at their default values. Therefore, effectively I get no data at all even when the client sends a perfectly valid Json string.
Do I need to do custom model binding?
Okay, I solved my problem and this might be an important insight for other programmers as well, because I learnt something while solving my own problem.
My solution:
I resorted to not using JSON and instead used the default encoding that HTTP uses for encoding form posted values, and then I used something that smacks of custom model binding (without actually creating a model binder).
Explanation:
Normally, when you make an ajax request and pass data from the client to any server platform, if you do not specify the encoding, i.e. the contentType parameter in the settings object of jQuery's ajax method (or if you are using any other means to make the ajax request other than jQuery, then however that thing sets the ContentType HTTP header is what you're after), HTTP encodes your posted data using its default encoding, which is much like what you post with a GET request after the query string, only it is binary encoded and not sent as a part of the URL.
If that's the case, and you're posting a collection of any type (IList, IEnumerable, ICollection, IDictionary, etc.) to ASP.NET MVC, then don't create an associative array in JavaScript to embody the collection. Don't even create an array. Don't create anything.
In fact, just pass it along inside the main big object using the convention:
data =
{ /*scalar property */ Gar: 'har',
/* collection */ 'Foo[index++].Bar' : 'value1',
'Foo[index++].Bar' : 'value2'
}
And don't use JSON. That will solve half your problem.
On the server side, receive the posted data in a FormCollection and use its IValueProvider methods (GetValue) to dig out the data you need. You don't have to explicitly create a model binder to do it. You can do it in a private method of your controller itself.
I will improve this answer a bit later when I can find more time.
Use Backbone or KnockoutJs for data binding.

Serialize an object containing a child object via JSON

I'm new with JSON and I try to pass some data in a JsonResult method located in my controller.
The method accepts an AModel as a parameter. This AModel is defined with some properties, and a BModel.
So, I wrote :
function loadDatas() {
var values =
{
"Title" : "The title",
"Text" : "Blahblahblah",
"BModel" :
{
"Summary" : "lorem ipsum",
"Page" : "15"
}
};
$.post("#Url.Action("Load", "MyController")",values,function(data)
{
// do stuff;
});
And my Load method is defined as :
[HttpPost]
public JsonResult Load(AModel test)
{
return Json(test); //dummy example, just serialize back the AModel object
}
When I put a breakpoint on Load's opening brace, test.Title and test.Text have the good values, but test.BModel.Summary and test.BModel.Page are null.
The worst part in this problem is if I put an alert(values.HousingModel.Summary); the value displayed is the good one ! Why is it not send correctly to my method whereas values.Title and values.Text are ??
I used this link to understand JSON format (http://www.sitepoint.com/javascript-json-serialization/) and mine seems valid... Isn't it ?
Thanks for your help
Alex
My Working Code
Action Method
[HttpPost]
public JsonResult Json(AModel test)
{
return Json(new { Success = true });
}
JQuery
$.ajax({
url : "#Url.Action("Load", "MyController")",
contentType : "application/json; charset=utf-8",
dataType : "json",
type : "POST",
data : JSON.stringify({test: values})
})}).done(function (result) {
//Success
}).fail(function (result) {
//Failed
}).always(function(result) {
//Always
});
Models
public class AModel
{
public string Title { get; set; }
public string Text { get; set; }
public BModel BModel { get; set; }
}
public class BModel
{
public string Summary { get; set; }
public string Page { get; set; }
}
Mistakes
Serialization was mising
Content Type was missing
Type was missing
Without seeing hour Model we can't give you definitive answer but...
is it possible that your BModel.Page is an integer in C#? If so the Model binder can't set your values on that SubObject with your javascript string value...

JSON Deserialization for polymorphic array in MVC 4 controller

Im using MVC 4 my ActionController recives the following Json:
{
"MainId": 1,
"Actions": [
{
"Attribute1ClassA": 1,
"Attribute2ClassA": 2
},
{
"Attribute1ClassB": 3,
"Attribute2ClassB": 4
},
{
"Attribute1ClassC": 5
}
]
}
and the Controller:
[HttpPost]
public ActionResult Commit(ActionsSummaryViewModel summary)
{
//DO stuff
}
and declaration for classes:
public ActionsSummaryViewModel
{
public int MainId {get;set;}
public IList<MainClass> {get;set;}
}
public class MainClass
{
}
public class ClassA : MainClass
{
public int Attribute1ClassA {get;set;}
public string Attribute2ClassA {get;set;}
}
public class ClassB : MainClass
{
public int Attribute1ClassB {get;set;}
public string Attribute2ClassB {get;set;}
}
public class ClassC : MainClass
{
public int Attribute1ClassC {get;set;}
}
So now, how can i manage the deserialization for the MainClass when the action controller receive the JSON ? because when i call the action the list items are null.
if part of the solution is Json.NET, how i can implement for MVC 4 controllers?
Thanks for your help.
You need a property or set of properties from which you can determine which type the class is to use this method. Using JSON.NET, I deserialize the incoming JSON as a dynamic object, then check the common property, determine the type, and deserialize the value again this type using my model type:
// I'm assuming here you've already got your raw JSON stored in 'value'
// In my implementation I'm using the Web API so I use a media formatter,
// but the same principle could be applied to a model binder or however
// else you want to read the value.
dynamic result = JsonConvert.DeserializeObject(value);
switch ((string)result.type)
{
case "typeone":
return JsonConvert.DeserializeObject<ModelOne>(value);
// ...
default: return null;
}
There's a little bit of extra overhead here because you're deserializing twice, but it's worth it in most cases to me because it's easy to understand what's going on and add new types as needed.
You could parse JSON into dynamic object instead using Json.NET:
using Newtonsoft.Json.Linq:
dynamic data = JObject.Parse("{ 'Name': 'Jon Smith', 'Address': { 'City': 'New York', 'State': 'NY' }, 'Age': 42 }");
string name = data.Name;
string address = data.Address.City;

Knockout and MVC3: Posting JSON to action, serializing twice? Can't convert to C# objects?

I've got a Knockout Model that gets posted via a save method:
self.save = function(form) {
ko.utils.postJson($("form")[0], self);
};
I check the request to make sure all the data is properly being posted (it is):
However, when I get to my action:
[HttpPost]
public ActionResult Create(EquipmentCreateModel equipmentCreateModel)
{
/stuff here
}
BuildingCode and Room contain escaped quotes, and identifiers is totally not null but has a count of 0:
And my ModelState is not valid, there is one error, for the Identifiers property which has an attempted value of :
and the Exception message is:
"The parameter conversion from type 'System.String' to type 'System.Collections.Generic.KeyValuePair`2[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' failed because no type converter can convert between these types."
My Model:
public class EquipmentCreateModel
{
//used to populate form drop downs
public ICollection<Building> Buildings { get; set; }
public ICollection<IdentifierType> IdentifierTypes { get; set; }
[Required]
[Display(Name = "Building")]
public string BuildingCode { get; set; }
[Required]
public string Room { get; set; }
[Required]
[Range(1, 100, ErrorMessage = "You must add at least one identifier.")]
public int IdentifiersCount { get; set; } //used as a hidden field to validate the list
public string IdentifierValue { get; set; } //used only for knockout viewmodel binding
public IDictionary<Guid, string> Identifiers { get; set; }
}
Now first I thought it was a problem with knockout, but then I found out the data wasn't being posted in the request correctly. I fixed that and still had the same problem. I thought MVC3 automatically converts Json now? Why are my simple properties appearing in escaped quotes and why can't my identities collection properly populate from the posted data?
Try this:
[HttpPost]
public ActionResult Create([FromJson] EquipmentCreateModel equipmentCreateModel)
{
//stuff here
}
where FromJson is:
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
This is taken from:
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
you should check the comments to as there are some modification for the FromJsonAttribute.
If you are using MVC3 you don't need to add JsonValueProviderFactory. For those of us who are still on MVC2 you can add JsonValueProviderFactory manually
http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx
However JsonValueProvider only works with AJAX post. For the full postback it needs the extra processing. There's a thread describing how to handle full postback: groups.google.com/d/topic/knockoutjs/3FEpocpApA4/discussion
The easiest solution would be to use AJAX post. Change
ko.utils.postJson($("form")[0], self);
to
$.ajax({
url: $("form").action,
type: 'post',
data: ko.toJSON(self),
contentType: 'application/json',
success: function (result) {
alert(result);
}
});
You could try:
[HttpPost]
public ActionResult Create(string equipmentCreateModelString)
{
var equipmentCreateModel = JsonConvert.DeserializeObject<EquipmentCreateModel> equipmentCreateModelString, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
}
Otherwise you need to use a JsonValueProviderFactory. Here's an example
#DOTang, i have another approach. First, you need a clean js object from your view model. You can get it calling: ko.mapping.toJS(self), then pass your view model to postJson function as a property. Finally add [FromJson] attribute to your controller. Your controller argument name, must be equal to your js property name, in this case: model.
I hope it works for you as works for me.
server
[HttpPost]
public ActionResult RegisterUser([FromJson] EquipmentCreateModel model)
{
//...
}
client
self.save = function() {
var jsModel = ko.mapping.toJS(self);
ko.utils.postJson('#Url.Action("Create", "Equipment")', { model : jsModel });
}

Create object with list of properties and pass it to Controller

Perhaps there is an easy solution for my problem but I simply cannot seem to find it. I have read lots of tutorials about Knockout so I get the basics but I ask this question because my entity-structure is a bit more complicated than a person with a name and a list of friends which may or may not be on Twitter (Video on Channel9: Helping you build dynamic JavaScript UIs with MVVM and ASP.NET). Here's my situation:
I have a class PersonnelClass with this basic structure:
[Serializable]
//The interface is for the implementation of 'Name' and 'Description'
public class PersonnelClass : IPersonnelClassOrPerson
{
public PersonnelClass() : this(Guid.NewGuid(), "", "") { }
public PersonnelClass(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
Id = id;
Name = name;
Description = description;
Properties = new PropertyCollection();
}
public Guid Id { get; private set; }
public String Name { get; set; }
public String Description { get; set; }
public PropertyCollection Properties { get; private set; }
}
The PropertyCollection class and associated AbstractProperty class look like this:
[Serializable]
public class PropertyCollection: List<AbstractProperty> { }
[Serializable]
public abstract class AbstractProperty: IEntity, IProperty
{
public AbstractProperty(String name, String description = null) : this(Guid.NewGuid(), name, description) { }
public AbstractProperty(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }
Id = id;
Name = name;
Description = description;
}
public Guid Id { get; private set; }
public String Name { get; private set; }
public String Description { get; private set; }
}
In my Controller, I create an instance of a PersonnelClassViewModel that has this structure:
public class PersonnelClassViewModel
{
public PersonnelClass PersonnelClass { get; set; }
public List<AbstractProperty> Properties { get; set; }
}
I fill this viewmodel with a new PersonnelClass and two test-properties to pass to my View like this:
var properties = new List<AbstractProperty>
{
new TextProperty("prop1", "descr1"),
new TextProperty("prop2", "descr2")
//TextProperty is derived from AbstractProperty
};
var vm = new PersonnelClassViewModel { Properties = properties };
return View(vm);
I get everything in my View as I wanted. From the View I want to create a new PersonnelClass with a set of selected properties. I have the fields for Name and Description and to add the properties I have a ListBox with the properties that already exist (for demo-purpose they came from the controller now). Through a bit of Knockout JavaScript code I can select items from this list and populate an HTML select-control () with the selected properties to add to the PersonnelClass. This all works fine, until I want to build up an object to pass back to the Controller and create the PersonnelClass.
My question is: what Knockout JS code is needed to build up this object and pass it to the Controller by submitting the form and in my Controller how should I receive this object, meaning: what type of object should this be (PersonnelClass, PersonnelClassViewModel, ...) ?
If any more info/code is needed, please do ask. Thanks in advance!
Update after answer of 'B Z':
I followed a few more of Steven Sanderson's tutorials about this to be sure I understand this, especially the one you provided in your answer. Now I have following code in my View to start with:
var initialData = #Html.Raw(new JavaScriptSerializer().Serialize(Model));
var viewModel = {
personnelClassViewModel : ko.mapping.fromJS(initialData),
properties : personnelClassViewModel.Properties,
selectedProperties : ko.observableArray([]),
addedProperties : ko.observableArray([])
};
ko.applyBindings(viewModel);
The variable 'initialData' contains the values I expect it to have but then I get the following error:
Microsoft JScript runtime error: 'personnelClassViewModel' is undefined
I have no clue anymore. Can anyone help me fix this?
Steven Sanderson has an example of how to to work with variable length lists and knockoutjs
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
Having said that, I think your problem isn't so much on the knockout side and more on the how to databind the data correctly on the server side. In the link above, Steven uses a FromJson attribute to model bind which you may find useful...
HTH

Resources