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.
Related
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.
Currently using MVC3 and using jQuery $.post function to send the ajax request to the controller action called "SearchInitiated". I'm having a little bit of a head-scratcher here because I'm not sure exactly where my issues lies. I'm sure its something minor that I have overlooked.
When I call my Controller method from an AJAX call, I am passing a json object (stringified) to a controller action. See below:
Request Headers from Chrome
Accept:/ Content-Type:application/x-www-form-urlencoded;
charset=UTF-8 User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64)
AppleWebKit/537.17 (KHTML, like Gecko)
Chrome/24.0.1312.52 Safari/537.17
X-Requested-With:XMLHttpRequest
Form Dataview
connectorId:1
sessionId:97e2d8b2-be9e-4138-91ed-42ef9c6a2cd6
party:
{"Tag":null,"PartyIdentifier":"1","Address":null,"Address2":null,"BusinessName":"","CellPhoneNumber":null,"CIF":"","City":null,"Country":null,"DateOfBirth":null,"EMailAddress":null,"EmploymentTitle":null,"EmployerAddress":null,"EmployerAddress2":null,"EmployerCity":null,"EmployerName":null,"EmployerPhoneNumber":null,"EmployerState":null,"EmployerZip":null,"Fax":null,"Firstname":"TEST","Lastname":"USER","MailingAddress":null,"MailingAddress2":null,"MailingCity":null,"MailingState":null,"MailingZip":null,"Middlename":null,"PhoneNumber":null,"State":null,"TIN":"1111111","WorkPhoneNumber":null,"Zip":null}
javascript
var parties = #(Html.Raw(Json.Encode(Model.SearchParties)));
function SearchForCustomer(id)
{
var currentSelectedParty = GetPartyById(id)
//SearchPost is a wrapper for $.ajax using default values for dataType and contentType
SearchPost(URL, {
'connectorId': '#Model.ConnectorId',
'sessionId': '#Model.SessionId',
'party' : JSON.stringify( currentSelectedParty )
}
}
Controller
public ActionResult SearchInitiated(int connectorId, string sessionId, SearchParty party)
{
code here....
}
public class SearchParty
{
public SearchParty();
public SearchParty(string lastname, string firstname);
public string Address
{
get;
set;
}
public string City
{
get;
set;
}
public string Country
{
get;
set;
}
public string DateOfBirth
{
get;
set;
}
.... etc etc
}
However, the party object is null.
If I change the code to the following, everything deserializes correctly into the strongly typed object.
public ActionResult SearchInitiated(int connectorId, string sessionId, string party)
{
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
SearchParty sp =
json_serializer.Deserialize<SearchParty>(party);
}
I know my json string is valid since it is working in the second code snippet and the value is being passed in the call. What else could I be missing?
Try this.
public class SearchParty
{
public string party { get; set; }
}
[HttpPost]
public ActionResult SearchInitiated(SearchParty party)
{
....
return View();
}
probably you need to set the traditional prop of jQuery.ajax to true in order to achieve traditional style of param serialization
put the below line of code immediatly after the document ready like
$(function(){
jQuery.ajaxSettings.traditional = true;
});
This SO question may help you further
I would make sure you have the [Serializable] attribute on your model. Also, make sure your request specifies party={myjson} .
You just need to declare a class 'SearchParty' in the controller to retrieve the strongly typed object in the controller without serializing.
public class SearchParty
{
public string party { get; set; }
}
public ActionResult SearchInitiated(SearchParty party)
{
code here....
}
Please check this link too
I resolved my error by modifying the javascript ajax call to this:
SearchPost(URL, JSON.stringify( {
'connectorId': '#Model.ConnectorId',
'sessionId': '#Model.SessionId',
'party' : currentSelectedParty
}))
I needed to stringify the entire data object sent in the ajax call, not just the 'party' object
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 });
}
I am trying to send AJAX POST request to an MVC Application
$.ajax({
type: 'POST',
dataType: 'json',
data: {"FirstName":"chris","LastName":"cane"},
contentType: 'application/json',
url: "http://dev.irp.com/irp.Ajax.Search/home/Foo",
success: function (data) {
alert(data);
}
});
This script is present on a different server on an ASP.NET application. My MVC App to handle the code is as below
[HttpPost]
public JsonResult Foo(fromclient test)
{
var obj = new SearchMemberServiceClient();
var members = obj.FindMember(test.FirstName, test.LastName, "", "", "", "").Members;
IEnumerable<Bar> sorted =
from a in members
orderby a.FirstName ascending
group a by new
{
a.FormattedFullName,
a.MembershipsProxy[0].GoodFromDate,
a.MembershipsProxy[0].GoodThroughDate,
} into k
select new Bar
{
FormattedName = k.Key.FormattedFullName,
goodfrom = k.Key.GoodFromDate,
goodthru = k.Key.GoodThroughDate,
};
return Json(sorted.ToList());
}
public class Bar
{
public string FormattedName { get; set; }
public DateTime goodfrom { get; set; }
public DateTime goodthru { get; set; }
}
public class fromclient
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
The problem is the script needs to post to that url and get the json data. But as the controller does not have any view, When I look inside the console on the client side it says 404 error for the url and also it says XMLHttpRequest cannot load http://dev.irp.com/irp.Ajax.Search/home/Foo. Origin http://web-dev.irps.com is not allowed by Access-Control-Allow-Origin.
I dont know if the problem has to do with the absolute path of the url for ajax request. If so how could I overcome this?
Due to the same origin policy restriction that't built into browsers you cannot send AJAX requests to different domains. A possible workaround is to have the server return JSONP instead of JSON. Here's an example of a custom JsonpResult that you could use in your controller action.
Can U try JSONP ? Why json? It's perfect to cross-domain.
I've just started looking at knockoutjs after watching the MIX 11 talk and it looks very promising.
I can understand how to pass your model back to your controller as json and update/save the model, but how can I pass my model to my view and make it observable?
For example if I have the following class:
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
}
I can pass it from my controller as json using a JsonResult so I send something like to my view:
{
firstName : "Bob",
lastName : "Jones"
};
Now, how do I make the properties observable and make this a viewModel within my code?
$.ajax({
url: 'Home/GetUserData',
type: 'post',
success: function (data) {
viewModel = ko.mapping.fromJS(data);
viewModel.save = function () { sendToServer(); };
ko.applyBindings(viewModel);
}
});
You will also need to use the mapping plugin.
http://knockoutjs.com/documentation/plugins-mapping.html
Notice the ko.mapping.fromJS(data); which is taking the model from the mvc endpoint and prepping it for observable.
Here's an article that helped me: http://www.codeproject.com/Articles/332406/Client-side-Model-binding-with-ASP-NET-MVC-3-and-K
It shows a way to bind viewmodel without ajax calls and without doing additional conversions at controller side.
I am not using the ko.mapping plugin. I think the mapping plugin works two-way (which is not your case).
I have declared an Html Helper >
public static string ToJson(this object obj)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(obj);
}
which serializes my server-side module to the client size JSON and declare it at the client end.
The accepted answer uses JQuery. This works perfectly well but isn't required. See:
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/