Json isn't returning a result that is accessible as an array - asp.net-mvc

I'm having troubles reading a Json result back from a controller method...
I have this method in my controller:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetCurrent()
{
IList<string> profile = new List<string>();
profile.Add("-1");
profile.Add("Test");
profile.Add("");
return this.Json(profile);
}
And it is being called by this jquery ajax post:
$.post("/Profile/GetCurrent", function(profile) { profileCompleteOpen(profile); }, "json");
and the javascript function called on the post's callback:
function profileCompleteOpen(profile) {
alert(profile);
alert(profile[0]);
}
The result of the first alert shows the array like this:
["-1","Test",""]
But the result of the second alert shows this:
[
rather than
-1
What am I doing wrong here... I've compared it to one of the other times I'm doing this and it seems to be the exact same. Why isn't it recognizing it's an array?
Thanks,
Matt

Try converting the json data in profile to a proper object by using eval() on it.
Example:
var profileObject = eval('(' + profile + ')');

Hmmm, I'd be doing what you're trying to do a little differently.
I'd either return a fully qualified object and then use it's properties;
class MyObj
{
public string name{get;set;}
}
fill the object and return it as a json object. then you're jquery code can access like any other object.
The other way might be to do a return PartialView("MyView", model);
That will return the partial view as html back to your page which you can then append to your html.

I think the type of profile is string instead of array. Why? Check the $.post method parameters. Maybe the problem is there.
$.post("url", null, function(profile) { ... }, "json");

Related

ASP.NET MVC: View sometimes returned as application/json instead of text/html

I have a view from which I call another view to render some json inside a script tag in my html:
public ActionResult App()
{
return View();
}
public JsonResult SomeJsonData()
{
// ... here goes the code that generates the model
return Json(model, JsonRequestBehavior.AllowGet);
}
inside my App.cshtml file I have something like this:
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController");
</script>
The problem is that sometimes when I reload the page in the browser (using Chrome 20 right now) it shows all the markup, and if I go to the Network tab in the developer tools I can see that the Content-Type of the page request was of type "application/json". If I just reload the page then it loads correctly (the content type is "text/html" as it should be).
Any idea on why does this happen? or what am I doing wrong?
When you return a JsonResult you are modifying the response Content-Type to application/json. So you first invoke the App controller action which returns a View and obviously sets the Content-Type to text/html and inside the returned view you call the SomeJsonData action which craps on the previous content type and modifies it to application/json. Of course the last one wins and that's what the user agent sees at the end of the day: application/json.
So, here's how to proceed:
public ActionResult App()
{
// ... here goes the code that generates the model
var model = ...
return View(model);
}
and in your strongly typed view:
#model MyViewModel
<script type="text/javascript">
var myJsonData = #Html.Raw(Json.Encode(Model));
</script>
Actually I just found another related question
calling #Html.Action for JsonResult changes my response type in parent template
I couldn't find anything before I posted.
The approach I'm gonna take is just changing the content type when returning the json data:
public JsonResult SomeJsonData(bool returnAsHtml = false)
{
// ... here goes the code that generates the model
return returnAsHtml ?Json(model, "text/html", JsonRequestBehavior.AllowGet) : Json(model, JsonRequestBehavior.AllowGet);
}
and on App.cshtml
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController", new {returnAsHtml = true});
</script>
And I'm adding also a flag to allow calling the actionmethod from other places that are expecting an application/json response.
Like other have said, the action that is returning JSON is changing the content-type of the response.
I was able to work around this by using a HtmlHelper to place the JSON into the page. I am avoiding using the controller and action; my HtmlHelper is calling a static method. This solution probably won't work in all cases and is kind of a hack, but you can avoid having to put the data into a bunch of view models this way.
namespace System.Web.Mvc.Html
{
public static class JsonDataProviderHelper
{
public static MvcHtmlString JsonDataProvider(this HtmlHelper helper, JsonDataType jsonDataType)
{
switch (jsonDataType)
{
case JsonDataType.YARDSALE_MINI_CALENDAR:
var yardsales = SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId);
return new MvcHtmlString(JsonConvert.SerializeObject(SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId)));
default:
return new MvcHtmlString("");
}
}
public enum JsonDataType
{
YARDSALE_MINI_CALENDAR
}
}
}
I am using json.net for serialization. I have the switch statement because I plan to use this helper to return JSON for other situations also.
And in the page:
<script type="text/javascript">
jQuery(document).ready(function () {
setupSmallSidebarCalendar(#Html.JsonDataProvider(JsonDataProviderHelper.JsonDataType.YARDSALE_MINI_CALENDAR));
});
</script>
In the page, the data is written in straight JSON, so no other parsing / massaging of the data is needed.
#Daran's found the cause - the ContentType gets overwritten with whatever was done last. His solution should work, however if you still like the Action method pattern, you should be able to do something like the following and simply change the ContentType before you return. It's still JSON data, but the content type plays nice with HTML views.
[HttpPost]
public JsonResult GetJson(int someId, int foobar)
{
JsonResult result = CreateResult(someId, foobar);
result.ContentType = "text/html";
return result;
}
The downside is that this is, well, a bit weird and/or unexpected behavior. I doubt this will work if you called the endpoint via AJAX, for example. But it looks like it would almost work and that would probably cause some confusion if it's in a shared codebase. For that reason, if you do this, it may be worth naming the endpoint in such a way that it isn't misused. You could even go so far as to create a new type of Result that is designed to work this way rather than use JsonResult.

MVC ActionMethod not finding passed in Value

I have a Create ActionMethod, something along the lines of:
[AcceptVerbs(HttpVerbs.Post)]
public ActionMethod Create(Journey journey)
{
if (Request.IsAjaxRequest())
{
//Save values
return Json(new { JourneyID = journey.JourneyID } );
}
}
The Journey object that I pass in is from my LINQ2SQL datamodel. I call the above ActionMethod from my Javascript using the JQuery.Post function, like:
var journeyData = {
CustomerID: $('#CustomerID').val(),
JourneyID: $('#JourneyID').val(),
EstimatedTravelTime: $('#EstimatedTravelTime').val(),
RouteName: $('#RouteName').val(),
.....
};
$.post('/Journey/Create',
journeyData,
function(jsonResult) {
//deal with result
},
'json'
);
The issue that I am having is that in the ActionMethod Journey.RouteName is always returning as null but the JSON that I pass back has a value, I check this using
alert(JSON.stringify(journeyData));
and in the resultant JSON object RouteName has a value, e.g. 'Go to work'. Any ideas why this wouldn't be being set in the ActionMethod? All other values that I pass back are being set fine.
Just a try and error suggestion:
First thing i would try is to rename "RouteName" param with somethign different, as "RouteName" is also a property of some MVC-redirect methods..
Have you examined your request JSON object that's going to the server?
Have you tried adding quotes to your string property value?
Like this:
var journeyData = {
CustomerID: $('#CustomerID').val(),
JourneyID: $('#JourneyID').val(),
EstimatedTravelTime: $('#EstimatedTravelTime').val(),
RouteName: '"' + $('#RouteName').val() + '"',
.....
};
There are a couple of things to consider. I'm guessing this is already in place, but your model must have a getter and setter on the RouteName property in order to be properly bound. Another thought is you might not be establishing the strong binding. This is usually done as part of the view's page declaration (e.g. Inherits="System.Web.Mvc.ViewPage") but I'm not sure this is happening prior to your post.

Can I pass a parameter with the OnSuccess event in a Ajax.ActionLink

When I use:
new AjaxOptions
{
UpdateTargetId = "VoteCount" + indx,
OnSuccess = "AnimateVoteMessage"
}
everything works fine...but I am trying to animate items in a list, with automatically assigned ID's. Since I want each of these to be addressable from my javascript, I believe I need to pass a parameter to my javascript. But when I use:
new AjaxOptions
{
UpdateTargetId = "VoteCount" + indx,
OnSuccess = "AnimateVoteMessage(2)"
}
I get an " Sys.ArgumentUndefinedException: Value cannot be undefined." exception. Well I get that when using the debug versions of MicrosoftMvcAjax.js. When using the compressed version I get a "Microsoft JScript runtime error: 'b' is null or not an object"
So my question is, can I pass a parameter to my javascript function using the OnSuccess event for a ActionLink?
Is this the right approach? How else am I going to have one javascript function have the ability to be run on 10 items (in my case the IDs of multiple DIVs) on my page?
There is a better way to do this - use the built in parameters that the OnSuccess call can be expected to pass
the built in parameters (the ones I found so far anyway) are data, status and xhr
data = whatever you return from the action method
status = if successful this status is just a string that says "success"
xhr = object that points to a bunch of javascript stuff that I will not be discussing...
so you would define your javascript like this (you can leave out the arguments you don't need - since all we want is our data back from the action we will just take the data argument)
function myOnSuccessFunction (data)
{
..do stuff here with the passed in data...
}
like I said before, data is whatever JSON that may be returned by the ActionResult so if your controller action method looks like this...
public ActionResult MyServerAction(Guid modelId)
{
MyModel mod = new MyModel(modelId);
mod.DoStuff();
return Json(mod, JsonRequestBehavior.AllowGet);
}
you would set up your action link like this...
#Ajax.ActionLink("Do Stuff", "MyServerAction", new { modelId = Model.Id }, new AjaxOptions { OnSuccess = "mySuccessScript(data);", OnFailure = "myFailureScript();", Confirm = "Are you sure you want to do stuff on the server?" })
this will create a confirmation message box asking if you want to actually invoke the action - clicking yes on the prompt will invoke the call to the controller - when the call comes back - the data argument will contain a JSON object of whatever you returned in your action method. Easy Peasy!
But wait! What if I want to pass another custom argument?! Simple! Just add your arguments to the front of the list...
instead of this...
function myOnSuccessFunction (data)
{
..do stuff here with the passed in data...
}
do this (you can have more than one custom argument if you like, just keep adding them as needed)...
function myOnSuccessFunction (myCustomArg, data)
{
..do stuff here with the passed in data and custom args...
}
then in your setup - just get the argument through some client side script within your ActionLink definition... i.e.
#Ajax.ActionLink("DoStuff", "MyServerAction", new { modelId = Model.Id }, new AjaxOptions { OnSuccess = "mySuccessScript(myClientSideArg, data);", OnFailure = "myFailureScript();", Confirm = "Are you sure you want to do stuff on the server?" })
Note that "myClientSideArg" in the OnSuccess parameter can come from wherever you need it to - just replace this text with what you need.
Hope That Helps!
or...a bit different syntax that worked for me:
OnSuccess = "( function() { MyFunction(arg1,arg2); } )"
There is a better way, which I believe is how Microsoft intended it: Set the AjaxOptions.OnSuccess to a function pointer, i.e. just the name of the function, no parameters. MVC will send it parameters automagically. Below is an example.
JScript parameter:
public class ObjectForJScript
{
List<int> Ids { get; set; }
}
Jscript function:
function onFormSuccess(foobar){
alert(foobar);
alert(foobar.Ids);
alert(foobar.Ids[0]);
}
View code:
#using (Ajax.BeginForm("ControllerAction", "ControllerName",
new AjaxOptions
{
OnSuccess = "onFormSuccess" //see, just the name of our function
}))
{
#Html.Hidden("test", 2)
}
Controller code:
public JsonResult ControllerAction(int test)
{
var returnObject = new ObjectForJScript
{
Ids = new List<int>{test}
};
return Json(returnObject);
}
Note that the parameter name in the JScript function doesn't matter, you don't have to call it "returnObject" even though the controller names it so.
Also note that Json() conveniently turns our List into a JScript array. As long as the methods of the C# object are translatable to Json, you can call them in the JScript like I've done.
Finally, the controller doesn't have to return a JsonResult, it can be ActionResult (but it's usually better to have a controller action do just one thing).
The above example will alert two objects (the Json and the array object which it contains), and then 2.
You can simply do this
Razor:
... OnSuccess = "AnimateVoteMessage('" + YourParameter + "')"
Please note the single quotes!
JavaScript:
function AnimateVoteMessage(YourParameter){
alert(YourParameter);
}
Enjoy,
Try this - it works for me:
OnSuccess = "new Function('MyFunction(" + myParameter + ")')"
Use this:
OnSuccess = "function(){yourfunction(" + productcode + ");}"
or
OnSuccess = "function(){yourfunction(this, " + productcode + ");}"
I ran in this issue too... and did not find a way to solve it!
I did a workaround (which is not nice but solved it for now... maybe until someone posts a solution here)...
what I did was place a hidden field in the TargetId div and OnSuccess I called a js method which retrieves the value from the hidden field <- this could happen in your AnimateVoteMessage method ...
See http://www.codeproject.com/KB/ajax/Parameters-OnSuccess.aspx
Basically:
function yourCallBack(arg1,arg2) {
return function(result) { // Your old function
alert(arg1); // param will be accessible here
alert(arg2); // param will be accessible here
alert(result);// result = the response returned from Ajax
}
}
Simply, at AjaxOptions, use the following:
OnSuccess = "onSuccessFunction(data, 'Arg1');"
Then at your function, you will get the new value as:
function onSuccessFunction(result, myNewArg) {
//Prints Arg1
Console.Write(myNewArg)
}

Post multiple parameters to MVC Controller using jQuery.post

I have a controller defined as:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address, DataContracts.GeoLocation geoLocation)
{
return Json("test");
}
where DataContracts.Address and DataContracts.GeoLocation are complex types.
From my View i'm trying to post using jQuery as such:
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
var JsonGeoLocation = {
"Latitude": $('Latitude').val(),
"Longitude": $('Longitude').val()
};
jQuery.post("/AddressValidation/PostMoreData", {address: JsonAddress, geoLocation: JsonGeoLocation}, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
However, on the controller, I get nulls.
It works if my Controller takes just 1 argument and I post just one object.
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address)
{
return Json("test");
}
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
jQuery.post("/AddressValidation/PostMoreData", JsonAddress, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
Any ideas how i can post more than one object?
Note that the "default serialization" that jQuery is doing here isn't going to work no matter what your controller does. jQuery doesn't "traverse" the parameter map below the first level, so the example in the question is likely generating this post data:
address=[object]&geoLocation=[object]
The other, working example does not contain any sub-objects, so it is being translated directly, like this:
Building=value&UnitNumber=value&...&MonthsAtAddress=value
The easiest fix is making the parameter map flat, each key prefixed with either 'Address.' or 'GeoLocation.', depending.
Thank you everyone for your input on this issue.
At this stage, we have departed from using jquery to post complex types to controllers. Instead we use the ms ajax framework to do that. ms ajax post nicely binds the complex types automatically out of the box.
So our solution now uses a mix of jquery and ms ajax framework.
Ash
Your code requires that the way jquery serializes an object is compatible with the MVC default model binder, which I think is unlikely.
If you can build your javascript object so that it serializes as a flat object with dot notation (JsonAddress.Building) that would work, or you can let jquery do the default serialization and then create a custom model binder to deserialize to the action parameter types.
I had the same problem and couldn't get anything to work. Also someone raised it as a bug with jquery and they closed it as not a bug.
I have found a few solutions which answer part of the whole question.
And the answer includes the following.
1) Client side: we would need to stringyfy all the objects you need to send. This could be a normal object or an array. It works on both.
2) Client side: You send the data as you have in the first post. As you would object by object.
Tip: When you send parameterised objects, jquery encodes the data sent to the server.
Following all are server side implementations
1) Deserializer class: which will take the input string and put it back in to object/list<>/IList<> what ever you have defined as datatype of the parameter of the controller function.
You would need to implement ActionFilterAttribute for the above.
2) Finally add an attribute to controller function, so that it uses the deserialiser class to get the parameters.
As this is quite a lot of code let me know if you need details or have you solved the problem.
Deepak Chawla

ASP.NET MVC How to pass JSON object from View to Controller as Parameter

I have a complex JSON object which is sent to the View without any issues (as shown below) but I cannot work out how Serialize this data back to a .NET object when it is passed back to the controller through an AJAX call. Details of the various parts are below.
var ObjectA = {
"Name": 1,
"Starting": new Date(1221644506800),
"Timeline": [
{
"StartTime": new Date(1221644506800),
"GoesFor": 200
}
,
{
"StartTime": new Date(1221644506800),
"GoesFor": 100
}
]
};
I am not sure how this object can be passed to a Controller Method, I have this method below where the Timelines object mirrors the above JS object using Properties.
public JsonResult Save(Timelines person)
The jQuery I am using is:
var encoded = $.toJSON(SessionSchedule);
$.ajax({
url: "/Timeline/Save",
type: "POST",
dataType: 'json',
data: encoded,
contentType: "application/json; charset=utf-8",
beforeSend: function() { $("#saveStatus").html("Saving").show(); },
success: function(result) {
alert(result.Result);
$("#saveStatus").html(result.Result).show();
}
});
I have seen this question which is similar, but not quite the same as I am not using a forms to manipulate the data.
How to pass complex type using json to ASP.NET MVC controller
I have also seen references to using a 'JsonFilter' to manually deserialize the JSON, but was wondering if there is a way to do it nativly though ASP.NET MVC? Or what are the best practices for passing data in this way?
Edit:
This method should no longer be needed with the arrival of MVC 3, as it will be handled automatically - http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx
You can use this ObjectFilter:
public class ObjectFilter : ActionFilterAttribute {
public string Param { get; set; }
public Type RootType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if ((filterContext.HttpContext.Request.ContentType ?? string.Empty).Contains("application/json")) {
object o =
new DataContractJsonSerializer(RootType).ReadObject(filterContext.HttpContext.Request.InputStream);
filterContext.ActionParameters[Param] = o;
}
}
}
You can then apply it to your controller methods like so:
[ObjectFilter(Param = "postdata", RootType = typeof(ObjectToSerializeTo))]
public JsonResult ControllerMethod(ObjectToSerializeTo postdata) { ... }
So basically, if the content type of the post is "application/json" this will spring into action and will map the values to the object of type you specify.
You say "I am not using a forms to manipulate the data." But you are doing a POST. Therefore, you are, in fact, using a form, even if it's empty.
$.ajax's dataType tells jQuery what type the server will return, not what you are passing. POST can only pass a form. jQuery will convert data to key/value pairs and pass it as a query string. From the docs:
Data to be sent to the server. It is
converted to a query string, if not
already a string. It's appended to the
url for GET-requests. See processData
option to prevent this automatic
processing. Object must be Key/Value
pairs. If value is an Array, jQuery
serializes multiple values with same
key i.e. {foo:["bar1", "bar2"]}
becomes '&foo=bar1&foo=bar2'.
Therefore:
You aren't passing JSON to the server. You're passing JSON to jQuery.
Model binding happens in the same way it happens in any other case.
A different take with a simple jQuery plugin
Even though answers to this question are long overdue, but I'm still posting a nice solution that I came with some time ago and makes it really simple to send complex JSON to Asp.net MVC controller actions so they are model bound to whatever strong type parameters.
This plugin supports dates just as well, so they get converted to their DateTime counterpart without a problem.
You can find all the details in my blog post where I examine the problem and provide code necessary to accomplish this.
All you have to do is to use this plugin on the client side. An Ajax request would look like this:
$.ajax({
type: "POST",
url: "SomeURL",
data: $.toDictionary(yourComplexJSONobject),
success: function() { ... },
error: function() { ... }
});
But this is just part of the whole problem. Now we are able to post complex JSON back to server, but since it will be model bound to a complex type that may have validation attributes on properties things may fail at that point. I've got a solution for it as well. My solution takes advantage of jQuery Ajax functionality where results can be successful or erroneous (just as shown in the upper code). So when validation would fail, error function would get called as it's supposed to be.
There is the JavaScriptSerializer class you can use too. That will let you deserialize the json to a .NET object. There's a generic Deserialize<T>, though you will need the .NET object to have a similar signature as the javascript one. Additionally there is also a DeserializeObject method that just makes a plain object. You can then use reflection to get at the properties you need.
If your controller takes a FormCollection, and you didn't add anything else to the data the json should be in form[0]:
public ActionResult Save(FormCollection forms) {
string json = forms[0];
// do your thing here.
}
This answer is a follow up to DaRKoN_'s answer that utilized the object filter:
[ObjectFilter(Param = "postdata", RootType = typeof(ObjectToSerializeTo))]
public JsonResult ControllerMethod(ObjectToSerializeTo postdata) { ... }
I was having a problem figuring out how to send multiple parameters to an action method and have one of them be the json object and the other be a plain string. I'm new to MVC and I had just forgotten that I already solved this problem with non-ajaxed views.
What I would do if I needed, say, two different objects on a view. I would create a ViewModel class. So say I needed the person object and the address object, I would do the following:
public class SomeViewModel()
{
public Person Person { get; set; }
public Address Address { get; set; }
}
Then I would bind the view to SomeViewModel. You can do the same thing with JSON.
[ObjectFilter(Param = "jsonViewModel", RootType = typeof(JsonViewModel))] // Don't forget to add the object filter class in DaRKoN_'s answer.
public JsonResult doJsonStuff(JsonViewModel jsonViewModel)
{
Person p = jsonViewModel.Person;
Address a = jsonViewModel.Address;
// Do stuff
jsonViewModel.Person = p;
jsonViewModel.Address = a;
return Json(jsonViewModel);
}
Then in the view you can use a simple call with JQuery like this:
var json = {
Person: { Name: "John Doe", Sex: "Male", Age: 23 },
Address: { Street: "123 fk st.", City: "Redmond", State: "Washington" }
};
$.ajax({
url: 'home/doJsonStuff',
type: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(json), //You'll need to reference json2.js
success: function (response)
{
var person = response.Person;
var address = response.Address;
}
});
in response to Dan's comment above:
I am using this method to implement
the same thing, but for some reason I
am getting an exception on the
ReadObject method: "Expecting element
'root' from namespace ''.. Encountered
'None' with name '', namespace ''."
Any ideas why? – Dan Appleyard Apr 6
'10 at 17:57
I had the same problem (MVC 3 build 3.0.11209.0), and the post below solved it for me. Basically the json serializer is trying to read a stream which is not at the beginning, so repositioning the stream to 0 'fixed' it...
http://nali.org/asp-net-mvc-expecting-element-root-from-namespace-encountered-none-with-name-namespace/

Resources