What we are going to do is define a template to render html using a standard ASP.NET User Control (.ascx file). This will get you full VS designer support, intellisense, data sources, and compilation checking. It does not require that you host the UserControl template on a page - instead the RenderView implementation dynamically creates a dummy Page object to host the UserControl while it renders, and captures and returns the html output as a string.
There are more than a few articles on this topic and most are about using the Repeater. In this post I will show you how to use the FormView also. Most reference ScottGu's Blog post “Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios” as their inspiration or that it provided the missing technique or knowledge to make it happen. In my case it was the later. I felt I should be able to use a user control to render html to do AJAX (AJAH) HTML injection on the client side. I had gotten real close, but this got me there much quicker.
First you need to enable PageMethods on the ScriptManager.
1: <ajaxToolkit:ToolkitScriptManager ID="ScriptManager1"
2: EnablePageMethods="true" runat="server" />
Now we will create a simple User Control using a Repeater. This is creating a customer grid using an ObjectDataSource. Notice the ObjectDataSource requires a CustomerID.
1: <asp:Repeater ID="rptCustomers" DataSourceID="odsCustomerDetail" runat="server">
2: <HeaderTemplate>
3: <table cellspacing="0" style="border-collapse: collapse;">
4: <tr>
5: <th>Customer Name</th>
6: <th>Contact Name</th>
7: <th>Phone #</th>
8: <th>Email</th>
9: </tr>
10: </HeaderTemplate>
11: <ItemTemplate>
12: <tr>
13: <td>
14: <%# Eval("CustomerName") %>
15: </td>
16: <td>
17: <%# Eval("ContactFullName") %>
18: </td>
19: <td>
20: <%# Eval("PhoneNumber", "{0:(###) ###-####")%>
21: </td>
22: <td>
23: <%# Eval("EmailAddr") %>
24: </td>
25: </tr>
26: </ItemTemplate>
27: <FooterTemplate>
28: </table>
29: </FooterTemplate>
30: </asp:Repeater>
31: <asp:ObjectDataSource ID="odsCustomerDetail" runat="server"
32: DataObjectTypeName="DDM.Common.Entities.CustomerInfoView"
33: OldValuesParameterFormatString="{0}"
34: SelectMethod="GetCompanyByCustomerID"
35: TypeName="DDM.Common.BLL.CustomerBLL">
36: <SelectParameters>
37: <asp:Parameter Name="customerID" Type="Int32" />
38: </SelectParameters>
39: </asp:ObjectDataSource>
In the code-behind of the UserControl that needs you to pass the CustomerID to the ObjectDataSource put this Property.
1: public int KeyID {
2: get { return Convert.ToInt32(odsCustomerDetail.SelectParameters[0].DefaultValue); }
3: set {
4: odsCustomerDetail.SelectParameters[0].DefaultValue = value.ToString();
5: fvMain.DataBind();
6: }
7: }
The Static Page Method in the code-behind of the page that will be displaying the html rendered by the UserControl is below. This will be called using JavaScript on the client-side of this page.
1: using System;
2: using System.Web.Services;
3: using System.Web.Script.Services;
4:
5: [WebMethod]
6: [ScriptMethod]
7: public static string GetCustomerDetail(int id) {
8: return ViewManager.RenderView(@"AjahControls\ConsumerInfo.ascx", id.ToString());
9: }
This is my ViewManager class. This enables you to not only render the html to a string using the UserControl, but will also enable you to pass the parameters needed for a data source or control the mode of a FormView.
1: using System;
2: using System.Web;
3: using System.Web.UI;
4: using System.Web.UI.WebControls;
5: using System.IO;
6: using System.Reflection;
7: using System.Text;
8:
9: namespace DDM.Web.AjahControls {
10: public class ViewManager {
11:
12: // Used to just load the User Control
13: public static string RenderView(string path) {
14: return RenderView(path, null, FormViewMode.ReadOnly);
15: }
16:
17: // This overload also allows you to pass the primary key value for the data source
18: public static string RenderView(string path, object keyID) {
19: return RenderView(path, keyID, FormViewMode.ReadOnly);
20: }
21:
22: /// <summary>
23: /// This is the main method. It able to support
24: /// FormView and data sources the need a keyValue
25: /// </summary>
26: /// <param name="path">This is the location of the User Control</param>
27: /// <param name="keyValue">This is the value needed for the data source</param>
28: /// <param name="formViewDefaultMode">FormView mode, ReadOnly, Update or Insert</param>
29: /// <returns></returns>
30: public static string RenderView(string path, object keyID, FormViewMode formViewDefaultMode) {
31: Page pageHolder = new Page();
32: pageHolder.EnableViewState = false;
33: UserControl viewControl;
34:
35: try {
36: viewControl = (UserControl)pageHolder.LoadControl(path);
37: } catch (Exception ex) {
38: throw new ApplicationException(ex.Message);
39: }
40:
41: foreach (PropertyInfo propInfo in viewControl.GetType().GetProperties()) {
42: if (propInfo.Name == "FormViewDefaultMode") {
43: if (propInfo.CanWrite) {
44: propInfo.SetValue(viewControl, formViewDefaultMode, null);
45: }
46: }
47:
48: if (propInfo.Name == "KeyID" && parameter != null) {
49: if (propInfo.CanWrite) {
50: propInfo.SetValue(viewControl, Convert.ToInt32(keyID), null);
51: }
52: }
53: }
54:
55: pageHolder.Controls.Add(viewControl);
56: StringWriter output = new StringWriter();
57: HttpContext.Current.Server.Execute(pageHolder, output, false);
58:
59: // This is used to remove the form that is required by
60: // some User Controls. By puttting these following two tokens in the
61: // user control just inside the form the code below removes
62: // anything above the startControl,
63: // and anything below the endControl.
64: string startToken = "<div id=\"startControl\" />";
65: string endToken = "<div id=\"endControl\" />";
66: string result = output.ToString();
67:
68: if (result.Contains("<form")) {
69: if (! (result.Contains(startToken) && result.Contains(endToken))) {
70: throw new ApplicationException("Missing a startToken or endToken for this control");
71: }
72:
73: result = result.Substring(result.IndexOf(startToken)+ startToken.Length + 1);
74: result = result.Substring(0, result.IndexOf(endToken));
75: result = result.Replace("\t", "");
76: }
77: return result;
78: }
79: }
80: }
This is the JavaScript code using jQuery on the client-side of the page that is displaying the html rendered by the User Control.
1: PageMethods.GetCustomerInfo(customerID, function(result) {
2: $('customerPanel')[0].html(result);
3: }, function(error) {
4: alert(error.get_message());
5: }
6: );
If you are going to use a FormView web control in a User Control that is going to render html it has a couple of issues. One is that it requires a form tag, and the other is that you would want to be able to control the mode of the FormView (ReadOnly, Update or Insert). By putting the form tag in this control you will now have two form tags and ASP.NET does not like this. To work around this issue you will see two div tags; one with and ID of “startControl”, and the other with an ID of “endControl”. These are needed so the ViewManager class will remove this form tag from the html that is render.
1: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="OwnerInfo.ascx.cs" Inherits="DDM.Web.AjahControls.OwnerInfo" %>
2: <form id="Form1" runat="server">
3: <div id="startControl" />
4: <asp:FormView ID="fvMain" DataKeyNames="OwnerID" DataSourceID="odsOwnerInfo" runat="server" >
5: <ItemTemplate>
6: <table>
7: <tr>
8: <td>Owner Name:</td>
9: <td><asp:Label id="lblFullName" runat="server" Text='<%# Eval("FullName") %>' /></td>
10: </tr>
11: <tr>
12: <td>Email Address:</td>
13: <td><asp:Label id="lblEmailAddress" runat="server" Text='<%# Eval("EmailAddr") %>' /></td>
14: </tr>
15: <tr>
16: <td>User Name:</td>
17: <td><asp:Label id="lblUserName" runat="server" Text='<%# Eval("UserName") %>' /></td>
18: </tr>
19: </table>
20: </ItemTemplate>
21: <EditItemTemplate>
22: <table cellpadding="2" cellspacing="0" border="0">
23: <tr>
24: <td><span class="required"> </span>First Name:</td>
25: <td>
26: <asp:TextBox ID="txtFirstName" runat="server" Text='<%# Bind("FirstName") %>' />
27: </td>
28: </tr>
29: <tr>
30: <td><span class="required">*</span>Last Name:</td>
31: <td>
32: <asp:TextBox ID="txtLastName" runat="server" Text='<%# Bind("LastName") %>' />
33: </td>
34: </tr>
35: <tr>
36: <td><span class="required">*</span>Email:</td>
37: <td>
38: <asp:TextBox ID="txtEmailAddr" runat="server" Text='<%# Bind("EmailAddr") %>' />
39: </td>
40: </tr>
41: <tr>
42: <td><span class="required">*</span>Confirm Email:</td>
43: <td class="formInput">
44: <asp:TextBox ID="txtConfirmEmail" runat="server" Text='<%# Bind("EmailAddr") %>' />
45: </td>
46: </tr>
47: <tr>
48: <td><span class="required">*</span>User Name:</td>
49: <td>
50: <asp:TextBox ID="txtUserName" runat="server" Text='<%# Bind("UserName") %>' />
51: </td>
52: </tr>
53: </table>
54: <div class="submitButtons">
55: <asp:Button ID="btSubmit" Text="Update Owner Information" runat="server" />
56: <input type="button" id="btReturn" name="btReturn" value="Cancel" onclick="cancel_onClick(1);" />
57: </div>
58: </EditItemTemplate>
59: </asp:FormView>
60: <div id="endControl" />
61: <asp:ObjectDataSource ID="odsOwnerInfo" runat="server"
62: DataObjectTypeName="DDM.Common.Entities.OwnerInfoView"
63: OldValuesParameterFormatString="{0}"
64: SelectMethod="GetOwnerByOwnerID" TypeName="DDM.Common.BLL.OwnerBLL">
65: <SelectParameters>
66: <asp:Parameter Name="applianceRepairID" Type="Int32" />
67: </SelectParameters>
68: </asp:ObjectDataSource>
69: </form>
This is the Static Page Method used to call the User Control that has a FormView.
1: using System;
2: using System.Web.Services;
3: using System.Web.Script.Services;
4:
5: [WebMethod]
6: [ScriptMethod]
7: public static string EditOwnerDetail(int id, string "Edit") {
8: return ViewManager.RenderView(@"AjahControls\OwnerInfo.ascx", id.ToString(), FormViewMode.Edit);
9: }
One of the benfits that was not seen when starting this adventure using User Controls to render your HTML for Ajax is that it makes the organization of all the code so much cleaner.