views:

130

answers:

5

I've been puzzling over some odd behaviour with my MVC2 project for the last few nights.

I have an MVC action result that accepts a project ID and a complex Json object, looking like this:

[HttpPost]
public JsonResult AddStory(int projectid, Story story)
{
    try
    {
        Project prj = repository.Single(p => p.ID == projectid);

        //prj.Stories.Add(story);
        //repository.SaveChanges();

        return Json(new { Result = story });
    }
    catch (Exception ex)
    {
        ErrorSignal.FromCurrentContext().Raise(ex);

        return Json(new { Result = 0 });
    }
}

My jQuery code for sending the complex object looks like this so far (complex object has more properties than this, but am trying only these two for debugging reasons):

$.ajax({
    url: '/Project/1/AddStory',
    data: { Summary: myStory.Summary, Size: myStory.Size },
    dataType: 'json',
    processData: false,
    traditional: true,
    type: 'POST'
});

My problem is that no matter how I post this object; even if I send it to a different controller and action, the request never seems to hit the server and the page is automatically redirected to the following URL:

http://localhost:57932/Project/1/Board?story.Summary=Test+description&story.Size=8&story.Priority=2&story.Deadline=08%2F31%2F2010&story-owner=http%3A%2F%2Ftestaccount.myopenid.com

Firebug's console shows an error in jQuery.min.js with the right headers, but no POST or RESPONSE values.

I've tried looking at the traffic in Fiddler and I can see that the request headers appear well-formed:

Accept application/json, text/javascript, /

And there is definitely an object being sent in the query string. So what am I missing? I'm sure there has to be a simple reason why things are getting so borked.

Edit:

Routes (from Global.asax) are the following plus the default mapped route:

    routes.MapRoute(
        "Project",
        "Project/{projectid}/{action}/{id}",
        new { controller = "Project", action = "Index", id = "" });
+3  A: 

Turns out the solution is simple. The ajax call in this current shape simply doesn't send any POST data due to processData = false setting. Change the jQuery to this:

$.ajax({
    url: '/Project/1/AddStory',
    data: { Summary: myStory.Summary, Size: myStory.Size },
    dataType: 'json',
    traditional: true,
    type: 'POST'
});

The jQuery API doco is a bit obtuse:

processData
Default: true

By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string, fitting to the default content-type "application/x-www-form-urlencoded". If you want to send a DOMDocument, or other non-processed data, set this option to false.

My interpretation is that it it's false, $.ajax will not convert the data object as name value parameters. Not sure what it's used for, but setting it to false seems like a bad idea :)

Igor Zevaka
A: 

i'd suggest you use $.post in stead

            var jsonbox = { Summary: myStory.Summary, Size: myStory.Size }
            $.post("/Project/1/AddStory", jsonbox, function doneit(data) {
                if (data.succes == true) {
                    //do something like return a message 'saved, all ok'
                } else {
                    /give an error.
                }
            }, "json");

i always use this style of action

    [HttpPost]
    public ActionResult SomeAction(int id, FormCollection collection) {
        try {
           //some code
        } catch (Exception e) {
            return Json(new { succes = false, error = "An error occured. Details: " + e.Message });
        }

        return Json(new { succes = true });
    }

$.post works good if you dont need anything fancy just a post, a variable to send and a function to execute when you get the result.

Stefanvds
+1  A: 

I don't have your full source code, so it is difficult to say why you have request redirection from '/Project/1/AddStory' to the

http://localhost:57932/Project/1/Board?story.Summary=Test+description&...

T tried to reproduce your problem. I created a small MVC 2 aplication with the action AddStory in the controller Project which looks like yours:

[HttpPost]
public JsonResult AddStory (int projectid, Story story) {
    return Json (new {
        Result = story,
        myNewProjectid = projectid,
        myNewSummary = story.Summary + ". " + "Bla bla",
        myNewSize = story.Size + 20
    });
}

where Story class I declared just like folowing:

public class Story {
    public string Summary { get; set; }
    public int Size { get; set; }
}

I inserted the route like you posted and added following jQuery sctipt in the Index view of the Home controller:

jQuery(document).ready(function () {
    var myStory = { Summary: 'Test description', Size: 8 };
    $.ajax({
        url: '/Project/1/AddStory',
        data: { Summary: myStory.Summary, Size: myStory.Size },
        dataType: 'json',
        success: function (data, textStatus, xhr) {
            alert('myNewSummary="' + data.myNewSummary +
                  '", Result.Summary="' + data.Result.Summary +
                  '", Result.Size=' + data.Result.Size);
        },
        error: function (xhr, textStatus, errorThrown) {
            alert("error");
        },
        type: 'POST'
    });
});

The code work without any problem and produce the message box with the text myNewSummary="Test description. Bla bla", Result.Summary="Test description", Result.Size=8 how expected.

You can continue to use traditional: true if you receive problems with posting more complex data structures. The current test don't need the usage of traditional: true.

To make you easy for you to compare your code with my working eample I placed full Visual Studio 2010 project under http://www.ok-soft-gmbh.com/ForStackOverflow/MvcApplicationJson.zip. I hope this helps you quickly to find th error in your code and fix it.

Oleg
A: 

try this:

$.post(
url: '<%=url.Action("AddStory","Project") %>', //the url
{ projectid: 1, story: { Name: 'cinderella'} }, // the parameters from the action
 function (data) { alert(data); } //success function
);

here I assumed that you have this class:

public class Story 
{ 
   public string Name {get;set;} 
}

one thing that I noticed in your code is that you have the parameters projectid and story but you are sending summary and size so the names don't match so the action method is not going to be hit

Omu
A: 

Ugh. PEBKAC issue.

Turns out the problem was because I'd previously had my input fields inside a set of <form> tags and jQuery was dutifully submitting my content - which doesn't need the projectId explicitly passed since that is part of the query string and is handled by MVC - and then, upon seeing the form, was actioning a "submit" call, which redirected back to the same page.

Sorry for wasting everyone's time. There was some really useful advice and a lot of practical ideas to troubleshoot my problem with.

Phil.Wheeler
Bounty awarded to @Oleg, as his answer most completely ties all the required components for my situation together for a workable solution.
Phil.Wheeler