ASP.NET MVC: Does JsonResult method require the Bind attribute? - asp.net-mvc

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.

Related

Invoke POST method using Breeze not working

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.

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.

RedirectToAction from different controller and with parameters/routevalues not working

I’m trying to call a action method from different controller but it´s not working. It simply skips the RedirectToAction
here's my code:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new { tempPolicy = TempPolicy });
}
Please help.
You cannot send complex objects when redirecting. You will have to include each property as query string parameter. And this works only with simply scalar properties.
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new
{
id = TempPolicy.Id,
prop1 = TempPolicy.Prop1,
prop2 = TempPolicy.Prop2,
...
});
}
If you have complex properties you will have to include them as well so that the default model binder is able to bind the model in the target action from the query string parameters:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new RouteValueDictionary
{
{ "id", TempPolicy.Id },
{ "prop1", TempPolicy.Prop1 },
{ "prop2", TempPolicy.Prop2 },
{ "prop3.subprop1", TempPolicy.Prop3.SubProp1 },
{ "prop3.subprop2", TempPolicy.Prop3.SubProp2 },
...
});
}
and your target action:
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
Another possibility is to persist this object in your backend before redirecting and then pass only the id:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
int id = Repository.Save(TempPolicy);
return RedirectToAction("Insert", "Policies", new { id = id });
}
and in your target action:
public ActionResult Insert(int id)
{
TempPoliciesUpload TempPolicy = Repository.Get(id);
...
}
I hope you have
public ActionResult Insert(TempPoliciesUpload tempPolicy)
action method in PoliciesController class.
Please see the overload of RedirectToAction here
Remove the parameter from the controller you're redirecting to and remove new { tempPolicy = TempPolicy }. See if that works (and then you localized the problem to parameter).
Most likely you need to cast it to the type of the action you redirecting to (hence Mark asked you for that signature) or play with it otherwise, maybe put in quotes (I doubt but good to try)
If even that doesn't work, check your spellings (this is why I do not like magic strings and love T4MVC) - but I doubt that either, naming looks correct.
Another likely possibility is something that solved for others here: RedirectToAction not working
Has anyone tried the first solution with complex objects?
I mean this one:
"...and your target action:..."
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
I don't think a RouteValueDictionary will convert or cast into a complex object. (Serialization must be used, I think)
My solution was passing the parameters as a RouteValueDictionary and receiving each parameters individually in the target action.
If you need to send a complex object you can try just returning the view from another controller like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return View("~Views/Policies/Insert.cshtml", TempPolicy );
}
If you want this view to post back to the correct controller method you will need to specify this in the 'BeginForm' Html Helper:
...
#using(Html.BeginForm("Insert", "Policy")
{
...
}
This really isn't best practice, but it is a workaround that you can use until you fix enough of the rest of your app so that you can use the redirects properly.
For decoupling, #Darin Dimitrov answer would be suited best. However if you do not wish to pass details in the URL, so that for example the user cannot fiddle with the data, you can use the short-lived persistence TempData feature like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
TempData["Policy"] = TempPolicy;
return RedirectToAction("Insert", "Policies");
}
Then retrieve it in the Insert:
public ActionResult Insert()
{
TempPoliciesUpload TempPolicy = (TempPoliciesUpload)TempData["Policy"];
}

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

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

How can you pass the model to the view?

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/

Resources