“This post and the demo solution is still valid for MVC 2.o.
However, a new version of this post using MVC 3.0 and Razor with minor improvements is available here.”
Continuing the previous post, let’s go ahead and implement the dialog extender for MVC forms (post actions).
The mechanism will be similar with some details added. In case of a for we need to post to the server. If the post action was not successful, that is if validations failed, we need to show validation errors in the dialog. In case of any other unexpected error, appropriate message needs to be shown.
Finally, if the post action is successful (all validations passed and data successfully saved), notify the client success so that the client can close the dialog. For this I created a dummy view edit success in the shared folder.
Let’s walk thru the implementation now -
The Model using the MVC framework’s validation provider-
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
namespace MVCDialogExtender.Models {
public class Product {
public Guid Id { get; set; }
[Required]
[DisplayName("Product name")]
public string Name { get; set; }
[Required]
[DisplayName("Product description")]
public string Description { get; set; }
}
}
The view (Edit.ascx):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCDialogExtender.Models.Product>" %>
<% using (Ajax.BeginForm("Edit", "Products",
new AjaxOptions { OnComplete = "addEditProductComplete", HttpMethod = "POST" })) { %>
<%: Html.ValidationSummary(true)%>
<%: Html.AntiForgeryToken()%>
<fieldset>
<legend>Product details</legend>
<div style="display:none;">
<%: Html.TextBoxFor(model => model.Id)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Name)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Name)%>
<%: Html.ValidationMessageFor(model => model.Name)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Description)%>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Description)%>
<%: Html.ValidationMessageFor(model => model.Description)%>
</div>
<p id="EditProductButtons">
<input type="submit" value="Save" id="SubmitAddEditProduct"/>
<input type="button" value="Cancel"/>
<!-- Implement what the cancel button is expected to do when not rendered in a dialog extender-->
</p>
</fieldset>
<% } %>
The Actions:
public ActionResult Edit(Guid id) {
var model = _data.SingleOrDefault(p => p.Id == id);
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product product) {
if (ModelState.IsValid) {
var list = ProductList;
list.Single(p => p.Id == product.Id).Name = product.Name;
list.Single(p => p.Id == product.Id).Description = product.Description;
ProductList = list;
return View("EditSuccessful");
}
else {
return View(product);
}
}
As you can see that the post action returns a dummy view called EditSuccess to notify success to the client. So here’s my dummy view(EditSuccess.ascx for the Shared folder:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %> "E-d-i-t---S-u-c-c-e-s-s-f-u-l"
And finally the dialog extender to bring it all together (only if required) at runtime. Also, consumes the hideous message from the dummy view – Mr “ProductEditDialog.ascx” from the Shared folder:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCDialogExtender.Models.DialogExtender>" %>
<div id="ProductEditDialogDiv" title="Product" style="display:none;"></div>
<script type="text/javascript">
$(document).ready(function () {
$('#ProductEditDialogDiv').dialog({
autoOpen: false,
width : '500px',
modal: true,
close: function (event, ui) { $("#ProductDetailsDialogDiv").html(""); },
buttons: {
"Save": function () { $("#SubmitAddEditProduct").click(); },
"Cancel": function () { $(this).dialog("close"); }
}
});
});
function openProductEditDialog(id) {
$.ajax({
type: "GET",
url: encodeURI('<%= Url.Action("Edit", "Products") %>' + "?id=" + id),
cache: false,
dataType: 'html',
error: function (XMLHttpRequest, textStatus, errorThrown) {
$("#ProductEditDialogDiv").html(XMLHttpRequest.responseText);
},
success: function (data, textStatus, XMLHttpRequest) {
$("#ProductEditDialogDiv").html(data);
},
complete: function (XMLHttpRequest, textStatus) {
// since these buttons are not specific to the dialog extender
$("#EditProductButtons").hide();
$('#ProductEditDialogDiv').dialog("open");
}
});
}
function addEditProductComplete(xmlHttpRequest, textStatus) {
var data = xmlHttpRequest.get_data();
if (data.indexOf("E-d-i-t---S-u-c-c-e-s-s-f-u-l", 0) >= 0) {
$('#ProductEditDialogDiv').dialog('close');
// call reload / refresh or any other post edit action.
}
else {
$('#ProductEditDialogDiv').html(data);
$("#EditProductButtons").hide();
}
}
</script>
As mentioned in the previous blog, since we’re only leveraging the extender pattern to make the code efficient, well structure and manageable, the naming convention a consistent naming convention is not just customary but mandatory. If not followed properly, the client side logic might get messy.
Finally, here’s dropping the extender as link on a random page:
<p>
<!--The link that will show the dialog-->
<a href="javascript:openProductEditDialog('4C341BA3-E971-43C0-8BB1-07F51320E10B');">Edit product details</a>
<!--The Dialog extender will sit here quitely-->
<% Html.RenderPartial("ProductEditDialog", new DialogExtender()); %>
</p>
And here’s, plugging it in a list:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MVCDialogExtender.Models.Product>>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>List</h2>
<table>
<tr>
<th></th>
<th></th>
<th>
Name
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<a href="javascript:openProductDetailsDialog('<%: item.Id %>');">Details</a>
</td>
<td>
<a href="javascript:openProductEditDialog('<%: item.Id %>');">Edit</a>
</td>
<td>
<%: item.Name %>
</td>
</tr>
<% } %>
</table>
<% Html.RenderPartial("ProductDetailsDialog", new DialogExtender()); %>
<% Html.RenderPartial("ProductEditDialog", new DialogExtender()); %>
</asp:Content>
The results:
The working Demo solution can be downloaded here.
Thoughts, opinions, ideas and comments welcomed.