Knockout.js & list of checkboxes: post to mvc controller - asp.net-mvc

I have an MVC view model that looks like this:
public class DirectorySearchModel
{
[Display(Name = "First name contains")]
public string FirstName { get; set; }
[Display(Name = "Last name contains")]
public string LastName { get; set; }
public CountriesCollection Countries { get; set; }
public IEnumerable<Country> SelectedCountries { get; set; }
public IEnumerable<Country> AllCountries { get; set; }
}
The CountriesCollection object (line 9) looks like this:
public class CountriesCollection
{
[Display(Name = "Countries")]
public int[] arrCountries { get; set; }
}
Now, I'm creating a new, blank instance of CountriesCollection and then adding it to a blank instance of the DirectorySearchModel view model and then serialising it all into a javascript view model for Knockout.js:
{
"FirstName":null,
"LastName":null,
"Countries":{"arrCountries":[]},
"SelectedCountries":[{"RelationshipManager":{},"CountryId":1,"CountryName":"UK"},{"RelationshipManager":{},"CountryId":2,"CountryName":"France"},{"RelationshipManager":{},"CountryId":3,"CountryName":"Spain"}],
"AllCountries":[{"RelationshipManager":{},"CountryId":1,"CountryName":"UK"},{"RelationshipManager":{},"CountryId":2,"CountryName":"France"},{"RelationshipManager":{},"CountryId":3,"CountryName":"Spain"}]
}
My checkboxes are rendered as: <input checked="checked" data-bind="checked: Countries.arrCountries" id="Countries_arrCountries30" name="Countries.arrCountries" type="checkbox" value="1">. Checking a couple means you end up with this Knockout.js view model:
{
"FirstName":null,
"LastName":null,
"Countries":{"arrCountries":["1", "3"]},
"SelectedCountries":[{"RelationshipManager":{},"CountryId":1,"CountryName":"UK"},{"RelationshipManager":{},"CountryId":2,"CountryName":"France"},{"RelationshipManager":{},"CountryId":3,"CountryName":"Spain"}],
"AllCountries":[{"RelationshipManager":{},"CountryId":1,"CountryName":"UK"},{"RelationshipManager":{},"CountryId":2,"CountryName":"France"},{"RelationshipManager":{},"CountryId":3,"CountryName":"Spain"}]
}
Submitting my view normally (i.e. via a submit button and not with Knockout.js) to an MVC action that expects a DirectorySearchModel, I'm able to ask for model.Countries.arrCountries to get a list of the checked items, but when I use...
$.post("/MyController/MyAction", ko.toJS(viewModel), function(returnData) {
$("#resultCount").html(returnData);
});
or...
$.post("/MyController/MyAction", viewModel, function(returnData) {
$("#resultCount").html(returnData);
});
to another action that expects the same DirectorySearchModel, model.Countries.arrCountries is always null! I wondered if it's due to Knockout.js posting the arrCountries entries as string[]s when MVC is expecting int[]s, but changing my MVC code to expect string[]s doesn't seem to change much..! The CountriesCollection object within the DirectorySearchModel appears to exist, but it's the arrCountries within that's always null.
Any ideas? Any help much appreciated!
Edit
The action that receives the Knockout.js viewModel:
public MvcHtmlString ResultCount(DirectorySearchModel model)
{
return new MvcHtmlString(getResultCount(model).ToString());
}
The getResultCount method:
public int getResultCount(DirectorySearchModel model)
{
IUserRepository userRepository = new UserRepository();
int count = userRepository.Search(model, null).Count();
return count;
}
FIXED!
Thanks to Konstantin for pointing out that a simple switch from $.post to $.ajax to send my Knockout.js view model back to my mvc action was all that was needed! Here's the $.ajax code I'm using:
$.ajax({
type: "POST",
url: "/MyController/MyAction",
data: ko.toJSON(viewModel),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
$("#resultCount").html(data);
}
});

You cant use $.post you need to go for the underlying $.ajax and add the correct contenttype to make mvc accept the posted json and do the model binding (contenttype should be "application/json; charset=utf-8") google for it and you will se lots of examples

Related

Angular.js cascading dropdown - mapping two models

I currently am struggling with mapping the value of a cascading dropdown -
with data driven by a tree constructed by an angular controller - to a primitive
string for posting back to a MVC controller.
I have a tree of data to populate the cascading dropdown, with the basic format of
public class Series
{
public string Text { get; set; }
public virtual ICollection<Depth> Depths { get; set; }
}
public class Depth
{
public int Id { get; set; }
public double Value { get; set; }
public string Text { get; set; }
}
Which is then mapped using angular to the output, which is an MVC type model:
public class LineItemViewModel
{
public string Series { get; set; }
public double Depth { get; set; }
}
Using an angular controller, where joistGroup is a list of series populated on controller creation.
HTML:
<div class="initialRow row"
ng-repeat="joist in floor.Joists">
<select name="series" class="form-control"
data-ng-model="series"
data-ng-options="s as s.Text for s in joistGroup.Series"
data-ng-change="joist.Series = series.Text">
<option></option>
</select>
<select name="depth" class="form-control"
data-ng-model="depth"
data-ng-options="d as d.Text for d in series.Depths"
data-ng-change="joist.Depth = depth.Value;"
data-rule-required="true">
<option></option>
</select>
</div>
Angular controller:
var app = angular.module('MaterialListModule', []);
app.controller('MaterialListController', function($scope, $filter, $http) {
$scope.getMaterialList = function() {
$http.get('url for get call')
.success(function(data, status, headers, config) {
$scope.Model = data;
});
}
$scope.getProducts = function (productSource) {
$http.get('url for get')
.success(function (data, status, headers, config) {
$scope.allItems = data;
$scope.joistGroup = $filter('filter')($scope.allItems, { Text: 'Joists' }, true)[0];
}).finally(function() {
$scope.getMaterialList();
});
}
$scope.getProducts("All");
});
Currently I am using ng-change to trigger an update of the primitive for posting back
to the MVC controller, but as this is both a create and edit page this doesn't work
for when I already have a selected series. I am thinking I can get it working by ng-init
but haven't had any success.
Can anyone point me in the direction of best practices to map a complex model for
cascading dropdowns to a primitive and back?
Instead of writing your ng-change inline, hook it up to a function. In that function, you can then access the 'complex object' and do whatever you want to do with it.
$scope.depthChange = function() {
$scope.myPrimitive = $scope.depth.Value;
//Do something with primitive
}
Also, when you have something already selected, on initialisation/retrieval of the data, just set the $scope.depth to the complex object that should be selected

Posting Custom objects to MVC3 action method via jQuery ajax

I have 3 classes with below definition
public class UserRole
{
public User User { get; set; }
public IEnumerable<Role> Role { get; set; }
}
public class Role
{
public int Id{ get; set; }
public string RoleName{ get; set; }
}
public class User
{
public int Id{ get; set; }
public string UserName{ get; set; }
}
This is the action method:
// GET: /Admin/UserManagement/Create
[HttpPost]
[Transaction]
public ActionResult Save(UserRole userrole)
{
}
Is there a way in Jquery to serialize some JavaScript vars into a class Obj and then send it to an MVC controller action via an AJAX post?
You could send a JSON request:
var data = {
user: {
id: 5,
userName: 'john'
},
role : [
{ id: 1, roleName: 'role 1' },
{ id: 2, roleName: 'role 2' },
{ id: 3, roleName: 'role 3' }
]
};
$.ajax({
url: '#Url.Action("Save")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(result) {
alert('success');
}
});
The JSON.stringify method is natively built into modern browsers but if you need to support legacy browsers you could include the json2.js script.
The easiest way to do it out-of-the box with all the parts happy (jQuery, MVC Framework) is to serialize the object graph as JSON data on the client and pass that into the POST. The MVC Framework is actually quite good at sorting the model binding out for you.
There is a downloadable sample with a similar complex object here:
http://theycallmemrjames.blogspot.ca/2010/05/aspnet-mvc-and-jquery-part-4-advanced.html
It deals with model binding in a number of different scenarios. Check out the suitcase example for your needs. Disclaimer: It's my blog, hope that doesn't offend.
Cheers.

How do I initialize a partial view so that getting different info shows the PartialViewResult containing the new info?

I have the following scenario:
I have a strongly typed partial view that display info for a view model called PlaceVieWModel it is rendered inside a strongly typed view for MainPlaceViewModel.
Classes:
public class PlaceViewModel
{
public int PlaceID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
public bool HasLiveMusic { get; set; }
}
public class MainPlaceViewModel
{
public string Name { get; set; }
public string Description { get; set; }
public List<PlaceViewModel> Places { get; set; }
public string WebPage { get; set; }
public string LogoRoute { get; set; }
public string LogoDetailCssClass { get; set; }
}
In the MainPlaceModelView view I generate an Ajax.ActionLink for each of the MainPlaceViewModel.Places that gets the info for that Place and displays it in the partial view, this is the code for that:
#foreach (var item in Model.Places)
{
#Ajax.ActionLink(item.Name, "", "", new AjaxOptions { Url = Url.Action("GetPlaceInfo", "MainPlaces", new { placeId = item.PlaceID }), HttpMethod = "GET", UpdateTargetId = "placeInfo", InsertionMode = InsertionMode.Replace, LoadingElementId = "loading" })
}
This is the action:
public PartialViewResult GetPlaceInfo(int placeID)
{
var place = _db.Places.SingleOrDefault(p => p.PlaceID == placeID);
PlaceViewModel placeViewModel = new PlaceViewModel();
placeViewModel.Address = place.Address;
placeViewModel.HasLiveMusic = place.HasLiveMusic;
placeViewModel.Latitude = place.Latitude;
placeViewModel.Longitude = place.Longitude;
placeViewModel.Name = place.Name;
placeViewModel.PlaceID = place.PlaceID;
return PartialView("_PlaceInfo", placeViewModel);
}
My problem is how should I initialize the #Html.Partial, if i do it like this:
<div id="loading" style="display:none;">
Loading...
</div>
<div id="palceInfo">
#Html.Partial("_PlaceInfo", Model.Places.FirstOrDefault())
</div>
The partial view always shows the first item no matter which Ajax.ActionLink is pressed.
Any idea on how can I initialize it so that it also works when any of the ActionLinks is pressed?
UPDATE: The problem was a typo on my target div, the id was "palceInfo" and I´m setting the action link to update a "placeInfo", I can´t believe I lost so much time because of this.
Assuming that GetPlaceInfo(int placeID) action works right (returns proper data), I do not see other errors besides possibly one.
UpdateTargetId on your action link is equal to "placeInfo". From msdn: UpdateTargetId Gets or sets the ID of the DOM element to update by using the response from the server.
You did not show your view in full, but I think that you are missing an element there with id="placeInfo". If this is the case, ajax simply does not have an element where it wants to put the result. Try placing your partial into a div with such id:
<div id="placeInfo">
#Html.Partial("_PlaceInfo", Model.Places.FirstOrDefault())
</div>
UPDATE:
#Html.Partial() runs only one time - when your page loads first time. If it gets you right data first time, you should not need to mess with it anymore. The div that contains that partial will get updated when you call your GetPlaceInfo method through an action link. So there are two places to check: your GetPlacesInfo action and the ActionLink.
First, check GetPlacesInfo. Put a break point on it, pass different ids to it and make sure you get right results.
Second, I do not use MS Ajax extensions (I do something different for my ajax calls), but try this:
#Ajax.ActionLink(item.Name, "GetPlaceInfo", "MainPlaces", new { placeId = item.PlaceID }, new AjaxOptions { UpdateTargetId = "placeInfo", InsertionMode = InsertionMode.Replace, LoadingElementId = "loading" })
Assuming "MainPlaces" is the controller name

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

Ajax POST using jQuery to MVC App on another Domain using JSON

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.

Resources