views:

6847

answers:

6

I have a View that allows a user to enter/edit data for a new Widget. I'd like to form up that data into a json object and send it to my controller via AJAX so I can do the validation on the server without a postback.

I've got it all working, except I can't figure out how to pass the data so my controller method can accept a complex Widget type instead of individual parameters for each property.

So, if this is my object:

public class Widget
{
   public int Id { get; set; }
   public string Name { get; set; }
   public decimal Price { get; set; }
}

I'd like my controller method to look something like this:

public JsonResult Save(Widget widget)
{
   ...
}

Currently, my jQuery looks like this:

var formData = $("#Form1").serializeArray();

$.post("/Widget/Save",
   formData,
   function(result){}, "json");

My form (Form1) has an input field for each property on the Widget (Id, Name, Price). This works great, but it ultimately passes each property of the Widget as a separate parameter to my controller method.

Is there a way I could "intercept" the data, maybe using an ActionFilterAttribute, and deserialize it to a Widget object before my controller method gets called?

+1  A: 

What you want to do is structure your javascript form object in the same way your backend object is structured:

{ Id : "id", Name : "name", Price : 1.0 }

Then use the toJSON plugin to convert it into the above string. You send this string to your backend and use something like the JayRock libraries to convert it to a new Widget object.

Sugendran
+3  A: 

Phil Haack has a good blog post about model binding that might be helpful. Not 100% what you're talking about here, but I think it might give you a better overall understand about the DefaultModelBinder.

Jeff Sheldon
+13  A: 

Thanks Jeff, that got me on the right path. The DefaultModelBinder is smart enough to do all the magic for me...my problem was in my Widget type. In my haste, my type was defined as:

public class Widget
{
   public int Id;
   public string Name;
   public decimal Price;
}

Notice that the type has public fields instead of public properties. Once I changed those to properties, it worked. Here's the final source code that works correctly:

Widget.aspx:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Widget.aspx.cs" Inherits="MvcAjaxApp2.Views.Home.Widget" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    <script src="../../Scripts/jquery-1.2.6.js" type="text/javascript"></script>   
    <script type="text/javascript"> 
    function SaveWidget()
    {
        var formData = $("#Form1").serializeArray();

        $.post("/Home/SaveWidget",
        formData,
        function(data){
            alert(data.Result);
        }, "json");
    }
    </script>
    <form id="Form1">
        <input type="hidden" name="widget.Id" value="1" />
        <input type="text" name="widget.Name" value="my widget" />
        <input type="text" name="widget.Price" value="5.43" />
        <input type="button" value="Save" onclick="SaveWidget()" />
    </form>
</asp:Content>

HomeController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

namespace MvcAjaxApp2.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
     public ActionResult Index()
     {
      ViewData["Title"] = "Home Page";
      ViewData["Message"] = "Welcome to ASP.NET MVC!";
      return View();
     }

     public ActionResult About()
     {
      ViewData["Title"] = "About Page";
      return View();
     }

     public ActionResult Widget()
     {
      ViewData["Title"] = "Widget";
      return View();
     }

     public JsonResult SaveWidget(Widget widget)
     {
      // Save the Widget
      return Json(new { Result = String.Format("Saved widget: '{0}' for ${1}", widget.Name, widget.Price) });
     }
    }
    public class Widget
    {
     public int Id { get; set; }
     public string Name { get; set; }
     public decimal Price { get; set; }
    }
}
MrDustpan
Awesome, glad you got it fixed up. And thanks for posting how you fixed it, that'll help other a lot in the future!
Jeff Sheldon
Public fields are the devil's spawn in .NET. They break everything. EVERYTHING I TELL YOU!
Will
Thanks. This helped me.
Ole Lynge
+5  A: 

Great post.

Note that (in MrDustpan's solution) the parameter name widget in the MVC Action method must match with the prefix used in the name attribute in the ASPX file.

If this is not the case then the Action method will always receive a null object.

<input type="text" name="widget.Text" value="Hello" /> - OK
<input type="text" name="mywidget.Text" value="Hello" /> - FAILS
Simon Sanderson
Excellent point and easily overlooked. Seems to be case sensitive too.
The Matt
A: 

Thanks man, you save me alot of time.

A: 

Thanks! This post just saved my life! :-D