This blog has, IMO, some great resources. Unfortunately, some of those resources are becoming less relevant. I'm still blogging, learning tech and helping others...please find me at my new home on http://www.jameschambers.com/.

Tuesday, May 4, 2010

ASP.NET MVC and jQuery Part 4 – Advanced Model Binding

This is the fourth post in a series about jQuery and ASP.NET MVC 2 in Visual Studio 2010.

image In my last post I covered the simple scenario of a Person object getting POSTed to an MVC controller using jQuery.  In this article, I’m going to look at three other, more complex examples of real-world model binding and where jQuery might fit in the mix.

The model binding scenarios I’ll cover are:

  • Sorting a list with jQuery UI support and submitting IDs to the controller for processing.
  • Using jQuery to augment the user experience on a list of checkboxes (supporting check all/none) and allowing MVC to handle the elegance on it’s own.
  • Give users the ability to build a list of items on a complex object, then submit that object to an MVC controller for processing.

The entire working solution is available for download at the end of the file.

Preface

In the first two examples here I have lists of data that are statically programmed into the page.  From my earlier posts in the series you can see how easy it is to generate partial views that would drive the UI elements from a database or other backend.  For brevity, I’m leaving those elements out of this example.

Sortable Lists

imageThe jQuery UI library provides the sortable() function which transforms the children of a selected element in the DOM to draggable, orderable items for the user to manipulate.

The sortable() extension also provides a mechanism for us to capture the order of the list via the toArray method.

Using the jQuery.ajax() method, we submit the results of the users’ efforts via post to a controller action.  For the purpose of this post, I’m not going to do any processing in the controller, but you can set breakpoints to see the data getting hydrated into the models.

Let’s start by setting up the controller action, which accepts a list of ints as a parameter to capture the order of the IDs.

image

Yes. It’s that simple.  Now, you’d likely want to do some processing, perhaps even save the order to a database, but this is all we need to catch the data jQuery.ajax() will throw at us.

The juicy bits in jQuery are as follows:

image

Note: In order to make the results of the array compatible with the binding mechanism in ASP.NET MVC (as at MVC 2.0) we need to use the ‘traditional’ setting in $.ajax().

There are a couple of interesting things to note here:

  • jQuery.ajax() by default makes requests to the current URL via get.  My controller action that accepts the List<int> is the same name as the basic view.  I change the type here to POST and the proper controller action is called.
  • The ‘toArray’ returns an array of the selected element IDs.
  • My unordered list contains list items with IDs that represent the unique items.  In this case, they are integers stored as strings (by nature of HTML).
  • The name of the list of IDs is passed in as the same name as the parameter in the controller action.
  • When submitted to the controller, the MVC framework finds the appropriate method, looks at the expected parameter type and sees the array sent by the client.  It then uses reflection and parsing (and maybe some voodoo) to coerce the values into the expected parameter type.

image

We can then use the list of IDs as required in the comfort of c#.

One of the interesting things that you can do, as the List<int> parameter is an IEnumerable, is that you can use LINQ to Objects on these guys without any effort.  Thanks MVC Framework!

Submitting Checkboxes

image How do you submit a list of the selected checkboxes back to an ASP.NET MVC controller action?  It’s all too simple.  In fact, the only reason I mention it here is to highlight some of the simplicity we inherit when we use the MVC Framework.

I actually laughed out loud when I figured this one out.  It’s that good (or, I’m that easily impressed).

For jQuery on this example, I’m only really going to use it to augment the user experience by providing a couple of buttons to check all or check none of the options.

We’ll use a standard HTML form and allow the user to select the items in a list they feel are appropriate.  The form will be submitted via POST to our controller action (named the same as the ActionResult for the original View) and our parameter will be automatically populated for us.

image

Some things to point out at this junction:

  • The values on the checkboxes here are the same strings that are displayed in the labels.
  • I have given all the checkboxes the same name. When submitted, MVC sees these as some kind of enumerable thing and will then try to bind based on that.
  • Optimus Prime is not a Jedi.
  • This the name used for the checkboxes is the same name as the parameter in the controller action.

Packing a Complex Model

image What about if you have a complex type with properties that can’t be expressed with simple HTML form fields? What if there are enumerable types as properties on the object? 

Man, am I glad you asked! The ASP.NET MVC Framework is pretty smart about taking items off the client Request and building up your objects for you in the hydration process.

Here is my Suitcase class, with a List<string> that will contain all of the things that someone wants to take along on their vacation.

image

So how do we get those items into the object?  The first step is to allow users to create them.  We do this with a simple textbox and a button, rigged up to some jQuery script as follows:

imageWhen the user enters an item and clicks ‘Add to suitcase’ (or hits enter) we grab a reference to the textbox.  Next, we use jQuery.append() to create a new LI element with the contents of the textbox.  Finally, we clear out the value and return focus to the input field.

When the user is finished loaded up their bags, we need to create a data map that will be submitted.  To simplify the process a little, we’ll first get that list of clothes together.

image

We first create an empty array.  Next we use jQuery.each() to loop through all the returned elements – the list of LI elements that the user has created – and add the text of those LIs to the array.

Next, we POST the data back to the server:

image

Here are some observations:

  • We’re POSTing and using the traditional setting so that the enumerated items are compatible with the current versions of jQuery and MVC.
  • The names of the properties in the Suitcase class are the names of the values we use in the data map submitted by jQuery.ajax().
  • As in the first example, jQuery.ajax() is posting to the default URL, which is the same URL as the view in this case.  In the controller we differentiate the action with the HttpPost attribute and, of course, the Suitcase parameter.

When the data is submitted we see this in the controller action breakpoint:

image

And the contents of the Suitcase.Clothes property:

image

Wrapping Up

There you have it: the basics of advanced…stuff. 

From here you should be able to work out most scenarios when building up objects in jQuery, submitting them to the controller and making a jazzier UI come together with jQuery UI while still using MVC in the backend.

Some things I’ve learned along the way:

  • Remember to watch the names of your variables in data maps! They have to match the parameters (or member properties) of the controller action.
  • If you’re having trouble getting things to submit and you’re not seeing any errors, try attaching FireBug in FireFox to the browsing session and see what’s happening to your requests/responses.
  • Make sure that you’re sending the values of the jQuery selections, and not the objects themselves if you’re having trouble binding.
    • Don’t send: $(“#my-textbox”)
    • Send: $(“#my-textbox”).val()

Resources

13 comments:

  1. Excellent post. Just what I was looking for.
    Thanks and keep up the great work!

    Chris

    ReplyDelete
  2. Great articles - keep them comming!!!!

    ReplyDelete
  3. Great article, I have a question. I have a class Test as follows:

    Public class Test
    public string Name{get;set;}
    public int ID{get;set;}
    }

    and In Class suitcase I have
    public Listtest {get;set;}

    and In View I have
    var data = new Object();
    data.test = new Array();

    var reading = {}; reading.Name = name;
    reading.ID = ID;
    data.test[i++] = reading;

    How do I send the data.test values to the controller?

    ReplyDelete
  4. The key thing is that any properties that you have on the client side, as you've generated, need to match either a property name on a class or a parameter name on an ActionMethod.

    If you're having trouble getting your objects to line up (as in, you think you have a controller method that should accept the data you're passing it) I would fire up FireBug and see if you can examine the post/get to see what parameters are getting passed in to the controller.

    Effectively, you're going to pass in your data.test object as the data map on a $.post() or $.ajax() call. The array will be serialized.

    A method signature that _MIGHT_ work could be similar to:
    public ActionMethod AcceptReadings(List readings) {}

    Now, do you want to send each reading separately, or as an array? You could make it much simpler to walkthrough if you looped through your array and made a POST for each reading.

    Hope this helps point you in the right direction!

    Cheers,
    -jc

    ReplyDelete
  5. Hi James,
    I m using jQuery to generate some data as HTML table when a Dropdownlist onChange event is triggered. How to pass the selected item's text and value to the controller's method?

    I m getting the formCollection as empty inside debug of the controller's method.

    - Cid

    ReplyDelete
  6. Hi, Thanks a lot. I want to send the reading as an array. Can I send you a mini project of what I want to do. I have tried it, but it`s not working as expected. I`m having the array as null in the controller.

    ReplyDelete
  7. I have modified the example only to test the value of test in the controller. The value of test is null in the controller whereas the other arrays are filled accordingly. What is wrong with my code?

    public class Suitcase
    {
    public string Color { get; set; }
    public string[] Size { get; set; }
    public List Clothes { get; set; }
    public List test { get; set; }
    }
    public class Test
    {
    public string Name { get; set; }
    public int ID { get; set; }
    }

    The view:

    $(function () {
    $("button").button();

    // allow users to add items to the suitcase

    // pack the suitcase up and send it to the baggage carosel...erm...controller
    $("#pack-it").click(function () {

    var clothesList = [];

    $("#clothes-list li").each(function () {
    clothesList.push($(this).text())
    });

    var SizeList = [];
    SizeList[0] = "Medium";
    SizeList[1] = "Large";
    SizeList[2] = "small";

    var Data = new Object();
    Data.test = [];

    var reading = {};
    reading.Name = "Micheal"
    reading.ID = 123;
    Data.test[0] = reading;

    reading.Name = "Rowen"
    reading.ID = 1234;
    Data.test[1] = reading;



    $.ajax({
    type: 'POST',
    traditional: true,
    data: {
    Color: $("#Color").val(),
    Size: SizeList,
    Clothes: clothesList,
    test: Data.test
    }
    });
    });
    });



    Controller:

    [HttpPost]
    public EmptyResult Suitcase(Suitcase lookWhatIPacked)
    {
    return new EmptyResult();
    }

    ReplyDelete
  8. Exactly what I've been searching for as I try to learn MVC/jquery. Is there a VB.NET version of this? One of the frustrations I have is the wealth of C# examples for MVC but hard to find in VB. Any way to get this in a VB version? Many thanks.

    ReplyDelete
  9. Hey Anonymous,

    No, I don't have a version of this in VB.NET, but feel free to send me your questions. I used to code in VB.NET and would be happy to help out as I can.

    Work through the IDE steps as presented in my other posts on the topic, and then walk through this. You'll find that most methods are easy enough to convert to VB without any coaching, but, if anything does come up, let me know and I'll do my best!

    Cheers,
    -Mister James

    ReplyDelete
  10. Awesome blog man, the things you have mentioned above are really informative and are examples of your awesome writing skills and a good blog.
    .net Obfuscator

    ReplyDelete
  11. hello..I just could connect to you with this way,

    thank you for your article, I am not good at jquery..I am using the sorting method you created for sorting a list of questions from mongodb.
    It can save correctly but when it processing at the end of Sorting Action, it will go on at the beginning of it..without redirect to the right place.the only difference i think is I am using razor ..could you kindly see my code and help? will highly appreciate!!

    Action:
    public ActionResult Sorting()
    {

    MongoCollection faq = MongoWrapper.GetCollection("FAQDetail");

    List FAQDetailList = new List();
    foreach (var myFaq in faq.FindAll().SetSortOrder(SortBy.Ascending("Sort")))
    {
    FAQDetailList.Add(myFaq);
    }


    return View(FAQDetailList);

    }


    [HttpPost]
    public ActionResult Sorting(List transformerIds)
    {


    try
    {

    MongoCollection faq = MongoWrapper.GetCollection("FAQDetail");

    List FAQDetailList = new List();
    foreach (var myFaq in faq.FindAll())
    {
    FAQDetailList.Add(myFaq);
    }



    foreach (var item in FAQDetailList)
    {
    for (int i = 0; i < transformerIds.Count; i++)
    {

    if (item.Sort == transformerIds[i])
    {

    var FAQCollection = MongoWrapper.GetCollection("FAQDetail");
    FAQCollection.FindAndModify(
    Query.EQ("Sort", item.Sort),
    null,
    Update.Set("Sort", i),
    true);

    break;
    }

    }

    }
    return RedirectToAction("Index");
    }
    catch
    {
    return View();
    }
    }

    $(function () {
    $("#sortable").sortable({
    placeholder: "ui-state-highlight"
    });

    $("#submit-list").b u t t o n();

    $("#submit-list").click(function () {
    $.ajax({
    data: { transformerIds: $("#sortable").sortable('toArray') },
    type: 'POST',
    traditional: true
    });
    });

    });


    and View
    @model IEnumerable



    Sorting

    @using (Html.BeginForm())
    {
    < p >
    < b u t t o n id="submit-list">Save< / b u t t o n> |
    @Html.ActionLink("Cancel", "Index")


    < u l id="sortable">

    @foreach (var item in Model)
    {
    < l i id="@item.Sort" class="ui-state-default">@item.FaqQuestion

    }


    < / u l >


    }

    ReplyDelete
  12. hello I just fix the FAQ part..my logic is a little bit wrong I should use Query.EQ("_id",item.FaqID), for search.


    thank you so much for your post you help me a lot

    happy holiday!!

    ReplyDelete
  13. Thank you so much for the nice tech!

    I used the Sort function, hope you could help me!

    I have been trying a lot to find a way to redirect to Index action in
    [HttpPost]
    public ActionResult SortedLists(List transformerIds){}
    but could not find the right way..
    It seems keep on looping this post SortedLists action until error

    I am using Razor..

    Could you please help me out?

    Thank you so much!

    ReplyDelete