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/
Related
I am working on single page application using HotTowel. I have referred below link for invoking POST method using breeze.
http://www.breezejs.com/breeze-labs/breezeajaxpostjs
Below is my code.
On Server side:
public struct Customer {
public string CompanyName{ get; set; }
public string Phone { get; set; }
}
[HttpPost]
public IQueryable<Customer> SimilarCustomersPOST(Customer customer)
{
return repository.CustomersLikeThis(customer);
}
Invoking POST method using breeze.
var query = breeze.EntityQuery.from('SimilarCustomersPOST')
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: { CompanyName: 'Hilo' , Phone: '808-234-5678' }
});
I am getting below error:
Error: The requested resource does not support http method 'GET'.
When I am writing a server code like below:
[System.Web.Http.AcceptVerbs("GET", "POST")]
[HttpPost]
public IQueryable<Customer> SimilarCustomersPOST(Customer customer)
{
return repository.CustomersLikeThis(customer);
}
It is invoking but accepted parameters getting null values.
Please let me know what is the reason I am getting this error.
Thanks in advance.
I'm not sure what happens when you mix [HttpPost] with [AcceptVerbs("GET")], but that might be the problem.
Note that in a GET method you need to use the [FromUri] attribute in front of parameters that are not simple value types, but you don't need that in a POST method. This blog post explains WebAPI parameter binding nicely.
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.
I have a ViewModel that I would like to use to populate the QueryString, almost the opposite of the binding that MVC does out of the box. So for the model that looks like this:
public class SearchViewModel
{
public string Keywords { get; set; }
// more properties here
}
I would hope to be able to do something like this:
string querystring = AspMagicMethods.GetQueryStringFromViewModel(searchViewModel);
// querystring == ?keywords=booyah&...
Obviously I could go through each property and create the string myself, but I was wondering if there's anything built into the framework that might be of assistance.
You can use following method of MVC to do so from any controller action.
var myModel = new SearchViewModel{Keywords ="test"};
RedirectToAction("actionName", "controllerName", myModel)
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 understood that this Bind attribute was not necessary, but JSON type is not binding without it. What am I missing here? I am running RC1.
Edit:
this is supposed to work:
public JsonResult Index(Person person)
{
do something with person.
}
But it won't work for some controller actions unless I do this:
public JsonResult Index([Bind(Prefix="")]Person person)
{
}
The first object is void.
jQuery Ajax:
$.ajax({
type: "POST",
url: "/Index/Person",
data: { PersonID: personID, Name: name },
dataType: "json",
success: function(data) {..}
}
}
});
The Bind attribute is not necessary for model binding in general. However, your question doesn't provide enough detail for anyone to make a judgment of what the actual problem is. If you're trying to post JSON to the server, then none of this will work. The default model binder only binds posted form values to arguments of the action method.
The code below works for me.
Also, just to check, are you sure you have at least the RC1 version of MVC? Early previews of MVC did require the Bind attribute, but that was changed in RC1 to make the [Bind] attribute unnessesary for common cases. See this post for details.
Javascript code:
$(function() {
$("#result").text("Calling Ajax...");
$.ajax({
type: "POST",
url: "/Home/Person",
data: { Name: "Erv Walter", PersonID: "123" },
dataType: "json",
success: function(data) {
$("#result").text(data.Name);
}
});
});
With this in the HTML:
<div id="result" />
Controller code:
[AcceptVerbs("POST")]
public JsonResult Person(Person person)
{
person.Name = person.Name.ToUpper();
return Json(person);
}
and the Person class looks like this:
public class Person
{
public string Name { get; set; }
public string PersonID { get; set; }
}
Do some sanity checking first. You probably have most, if not all of these, but without a little more code we're kind of in a bind:
Do you have the [AcceptVerbs(HttpVerbs.Post)] filter on your action? Try marking Person as [Serializable] too. What does your Person look like? Does it have a default constructor? Is there anything going on in the default constructor that might cause something to error out? Make sure you are using the exact same spelling on the properties on Person
Try making an action that doesn't use Person as an argument:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Index(int PersonID, string Name) {}
Or take a FormCollection and call UpdateModel().
The problem was that the parameter variable name was the same name (but in lowercase) as a variable in the object.