开发者

Commonly-accepted approach in MVC for form within jQuery dialog

There seem to be several ways to integrate jQuery dialogs with ASP.NET MVC. Has a specific approach emerged as the commonly-accepted best-practice way to do so?

As an example: I have a list page where clicking "edit" for any of the listed items opens a form in a jQuery dialog, populated with the item's details. User edits details and clicks "save". If the save succeeds on the server-side, the dialog is closed and the list is re-built with fresh data. If the save fails on the server-side, the dialog remains open and displays the error message(s) to the user.

  1. No-JSON approach: each "edit" link is an HREF to an "edit" controller action. That controller action builds a view that is identical to the "list" view, plus it includes a partial action to build the edit form, populate it, and define the javascript to pop it open as a jquery dialog. The "save" is a form-post; if it succeeds, it returns a redirect action back to the list page. If it fails, it rebuilds the entire page (including the form that pops up in a dialog) displaying the error messages as well.
  2. All-JSON approach: the list page renders an empty edit form (hidden), ready to be popped open into a dialog. The "edit" link calls local javascript which does an ajax request to get the full object (I define a controller which returns the full object as a JsonResult). On success, it fills the edit form with the object's data and opens the dialog. The "save" link calls local javascript which bundles the form data into a json object, and calls a post operation with that json object as payload (I define a controller which expects that object, attempts the save, and returns a JsonResult indicating success/failure+errorMessages). Success callback from the ajax request evaluates the returned object, and either shows the error messages in the still-open jquery dialog, or closes the dialog and reloads the "list" page to get fresh data in the list.
  3. [Edit] Ajax-HTML approach: just saw this SO discussion which describes another approach. The "edit" calls local javascript which does an ajax post to get the FULL HTML of the dialog (I would write a controller which returns a partial view: the fully-populated form). It renders the returned HTML into a jquery dialog, and also "re-wires" the form submission to do an ajax-post of the form's contents (I would write an httpPost controller, same as in #2 above). The success callback evaluates the response and populates error messages or closes the dialog.
  4. Some other cool approach I haven't thought of?

Option 1 seems to be more in keeping with "pure" ASP.NET MVC. However, it seems to feature big HTTP payloads (since we're shipping the full page back to the browser on every request), and slower server-side performance (since we're re-building the list on every request).

Option 开发者_开发技巧2 seems to be more consistent with more modern Ajax-based web applications (smaller HTTP payloads, more granular operations). However, it seems like many of the controllers will be JSON controllers, and I'll be writing a lot of client-side code to marshal data from JSON objects to/from form fields, display error messages, etc. It also seems like I'd be missing out on a lot of cool MVC features like EditorFor() and ValidationMessageFor(). It just "feels" like I'm working around the MVC system instead of with it.

[Edit: added option 3] Option 3 seems to be a bit of a hybrid between 1 and 2. I use the "pure" MVC approach to build and populate the form, and return a fully-formed HTML FORM tag. Returning HTML to an ajax request feels weird since it's so verbose, but I can get over it. The post operation is nice, compact JSON which "feels" better over ajax. However, it's unfortunate that the payload object is a FormCollection rather than a real viewmodel object. It seems like I can make use of some of the MVC conveniences (EditorFor()) but not others (ValidationMessageFor()).

I'm looking for the "right" way to do this, not just the fastest way to hack it together. Yeah, yeah, I know there's no universally "right" way. But I'm sure there's some notably wrong ways to do it, and I want to avoid them.

I'm pretty experienced in ASP.NET/C#, but I'm pretty new to MVC. Thanks in advance for your help!

[Edit] - outstanding responses - I wish I could award multiple answers/bounties, as I found several responses extremely useful. But since I can't, I'm marking the top-voted response as the answer. Thanks again to all respondents!


My team and I have a lot of experience writing AJAX enabled MVC apps, and we have used all 3 approaches.

However, my favorite is definitely the AJAX-HTML approach -- Use a PartialView to render the contents of the dialog, which could include server-side validation messages and any other logic.

The biggest benefit of this approach is the separation of concerns - your Views are always responsible for rendering your HTML, and your JavaScript doesn't have to contain any text, markup, or "templates" needed to render the JSON.

Another big advantage is that all the great MVC features are available for rendering the HTML: strongly-typed views, HtmlHelper, DisplayFor and EditorFor templates, DataAnnotations, etc. This makes it easier to be consistent and lends well to refactoring.

Just remember, there's no requirement to stick to a single approach. When your AJAX call only needs something simple, such as a status update like "Success", it's fine to just use a string or JSON to convey those messages. Use PartialViews when HTML is needed, and use simpler methods when communication is needed.


Your 2nd method, the All-JSON approach, seems to be increasingly prevalent with MVC and MVVM client side libraries like Knockout

In this you could actually have all of the data in JSON (including the list) and edit list items (similar to their list item editor demo, just with a dialog rendering isntead of inline, and bind the data to readonly spans in your cells) and then serialize the entirety of the set back to the server on save. Or you could do it with piece-meal saves after each popup edit.

JSFiddle: http://jsfiddle.net/paultyng/weLtH/17/

The JS could be cleaned up a bit, I didn't include a save button, but you should get the idea. The edit dialog could be a single template bound to one edit as well, instead of doing per row, this was just the simplest way to do it with Knockout.


I think the best way would be to render the list normally. Hook up the edit links to go to a separate page (follow me here) like you would normally do.

With JS handle clicking of the link, and do a get to it's href. In the edit action do a check for Request.IsAjaxRequest() and if it is, return a partial view if it isn't, return the full view. Or render the normal edit view without the master page (pass in null to the master page parameter in the View() call or call return Partial()). Take the contents of the result and put it into a dialog.

Also use JS to handle submitting the form and getting the result from the request. If it wasn't successful insert the contents of the view into the dialog to show that there were errors. Otherwise close it and move on.

The benefit to this approach is it's very unobtrusive and still allows functionality for those who don't have JS.


Ok, so your options are pretty much torching the concept of progressive enhancement here. 2 & 3 aren't going to work if your client doesn't support java script. Obviously thats ok if you don't care, but I think I'd try to engineer things so that they degrade gracefully and you are asking for best practise here.

So the way I would construct it, starts with your option 1. You have edit buttons that trigger another action which loads an edit page, and this page is designed with all the validators etc as per normal mvc. That's your base functionality so works with no js.

So then the next question is how do we progressively enhance this to have a nice popup instead of a new page?

Well first step is to create a handler to open your dialog attached to the edit links on click (make sure to e.PreventDefault). Now to save too much coding effort I would be looking to reuse the edit page. This is going to require a bit of refactoring though as you don't want to include the layout for the ajax requests. You can do this a few ways, but I think the cleanest is to have the edit area of the edit view as a partial view which the main edit view uses to render its model.

Then, in your edit action, you can check if you have an ajax request, if so then return a PartialView(mypartialeditview) or a View(editview) if not.

In terms of then submitting the results back to the server if you want an easy life just treat it like a form. You can use the micorsoft unobstrive ajax here and it will be very simple. You use Ajax.BeginForm in your partial view. n.b.This will degrade to a normal form if ajax not available. Have the AjaxOptions for this beginform set to update the div in the dialogue so if it responds with its html you don't have to do anything more, it implies a validation error.

[small aside as you asked above about this: In terms of the HttpPost handler, the default model model binder is amazingly smart, it can bind form fields to properties on a complex class object parameter. This also work with json so you don't have to end up with lots of action methods to support different scenarios.]

So if your update is unsucessful the post handler will return the partial view again, bound to the model so you'll get all your validators. If however the update is successful I would suggest that rather than return anything, the action does a redirect back to your main page which you want to reload anyway as you've changed the underlying.

If you don't like doing a full reload of the main page, then it gets more complex as with the approach above you are returning html. You will either have to jquery over that to find a hidden field or class to indicate success/failure or migrate to a pure json approach which returns a jsonresult. This is getting heavier on the maintenance/coding front though. I'd probably do the jquery check and have it wired to the completion handler of the ajax.Beginform.

If you really want to get your head around this stuff, I have found the Steve Sanderson Pro Asp.net MVC book invaluable. The one I initially read was for MVC2, but just in process of reading the MVC3 update. I have mixed feelings on the update, as its been simplified in a few places - easier to follow now but I feel like a few things are missing, also it was rushed as some errors etc as it gets near the end. I guess maybe they panicked now that MVC4 is being talked about and book wasn't out! Still a good book though and it covers all this stuff beautifully.

Hope this helps. Appreciate it covers some of the same ground as an answer above but hope I've drilled in more for you.


Using jQuery.tmpl() plugin1

I use a similar scenario but do it in a different way.

I have a master list where each item is wrapped in a container (be it table TR or DIV). Some general information about the item is displayed in the master list when there's too much data to contain in a simple list. Hence a master list and not details (of course).

Each item container is usually written this way:

<div class="item" data='<%= item.ToJson() %>'> <!-- single quotes! -->
    item visible list data
</div>

The main part here is my custom extension method ToJson() that serializes my item data which can easily be used on the client.

Then I have a jQuery template at the end of a list contained inside a script tag. This template is the actual content of the edit dialog with all item variables set as needed.

Whenever user clicks edit on the item, I simply parse item JSON by:

// in edit link click handler
var itemData = $.parseJSON($(this).closest("[data]").attr("data"));
// display dialog
displayDialog($("#editDialog").tmpl(itemData));

My dialog than takes care of the rest via Ajax calls and closing the dialog when calls are successful or user cancels editing.

This keeps my HTML code to minimum (having only one template) and JSON also contains only those properties that are actually needed.

What to do when your model class has other entity references?

This is quite common that entities are related between each other. Suppose you have a Post and Comment entities:

public class Post
{
    public int Id { get; set; }

    public string Body { get; set; }

    public IList<Comment> Comments { get; set; }
}

Of course you're not interested in related entities when converting your items to JSON on the server. That's why you can put an attribute to related properties so they don't get included in the JSON:

public class Post
{
    public int Id { get; set; }

    public string Body { get; set; }

    [ScriptIgnore]   
    public IList<Comment> Comments { get; set; }
}

This will make JSON serializer ignore the related property we won't edit in our details editor.

ToJson() extension code

The last thing to put here is extension method code so here it goes:

public static class ObjectExtensions
{
    /// <summary>
    /// Serializes this object instance to JSON string.
    /// </summary>
    /// <param name="instance">Object instance being extended.</param>
    /// <returns>Returns a JSON string that represents current object instance.</returns>
    public static string ToJson(this object instance)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        // register custom class converters
        serializer.RegisterConverters(...);
        return serializer.Serialize(instance);
    }

    /// <summary>
    /// Serializes this object instance to JSON string.
    /// </summary>
    /// <param name="instance">Object instance being extended.</param>
    /// <param name="recursionDepth">Serialization recursion limit depth.</param>
    /// <returns>Returns a JSON string that represents current object instance.</returns>
    public static string ToJson(this object instance, int recursionDepth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        // register custom class converters
        serializer.RegisterConverters(...);
        serializer.RecursionLimit = recursionDepth;
        return serializer.Serialize(instance);
    }
}

What to do when entities are too complex?

Upper approach is useful when your entities aren't too complex. In cases when:

  • your entities are complex or have long data (long in terms of serialized string)
  • determining JSON objects for all items would be too time consuming
  • unknown JSON object at time of master list display

then you can always send a remote Ajax call to server for a single entity and return either

  • JSON object to use with dialog template
  • return partial view with editor

Second approach is better, since converting object instance to either HTML or JSON is practically similar task. but in the second case there's no need for client-side template processing.

But don't use remote requests just because you can. Use an approach that's easier and better for the problem at hand.

1: jQuery template API reference

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜