Automating repetitive markup generation significantly accelerates the development of data-entry interfaces. By combining CodeSmith with a structured XML configuration file, developers can dynamical render Ext.NET Web Forms controls based on metadata definitions rather than manually coding each component. The following implementation demonstrates how to parse layout instructions, map control prefixes to specific Ext.NET widgets, and output a fully functional user control with integrated validation states.
Template Configuration and Assembly References
The automation pipeline begins with a CodeSmith template that declares the required .NET assemblies for XML manipulation, design-time UI editors, and schema exploration. Exposed properties allow the developer to specify the target control class name and the file path to the XML layout definition.
<%@ CodeTemplate Language="C#" TargetLanguage="Html" Debug="True"
CompilerVersion="v3.5" Description="Generates Ext.NET WebForms controls from XML metadata" ResponseEncoding="UTF-8" %>
<%@ Assembly Name="System.Xml.Linq" %>
<%@ Assembly Name="System.Design" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Import Namespace="System.Xml.Linq" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Text" %>
<%@ Import Namespace="System.Windows.Forms.Design" %>
<%@ Import Namespace="SchemaExplorer" %>
<%@ Property Name="TargetControlName" Type="System.String" Default="" Optional="True" Category="Parameters" Description="Output user control class name." %>
<%@ Property Name="PrimarySchemaTable" Type="SchemaExplorer.TableSchema" Category="Context" Description="Primary data table for schema mapping." %>
Core Rendering Engine
The template logic resides in a server-side script block. It loads the specified XML document, iterates through defined field groups, and translates metadata attributes into corresponding Ext.NET markup. The implementation calculates column distribution ratios and evaluates control identifier prefixes to instantiate the appropriate input type.
<script runat="template">
private string _layoutDefinitionPath = string.Empty;
[Editor(typeof(FileNameEditor), typeof(System.Drawing.Design.UITypeEditor)), Category("Setup"), Description("Location of the XML layout definition.")]
public string LayoutDefinitionPath
{
get { return _layoutDefinitionPath; }
set { _layoutDefinitionPath = value; }
}
public string GenerateLayoutHtml()
{
if (!File.Exists(_layoutDefinitionPath)) return string.Empty;
StringBuilder markupBuffer = new StringBuilder();
XElement configRoot = XElement.Load(_layoutDefinitionPath);
var fieldSections = configRoot.Descendants("Group");
int sectionCounter = 0;
if (fieldSections == null || !fieldSections.Any())
{
BuildFieldCells(markupBuffer, configRoot.Descendants("Field"));
}
else
{
foreach (XElement section in fieldSections)
{
string panelId = section.Attribute("GroupID") != null ? section.Attribute("GroupID").Value : "fpGroup" + sectionCounter;
string panelTitle = section.Attribute("Title") != null ? section.Attribute("Title").Value : string.Empty;
string colSetting = section.Attribute("Columns") != null ? section.Attribute("Columns").Value : "1";
string colRatio = "0.5";
switch (colSetting)
{
case "1": colRatio = "1"; break;
case "2": colRatio = "0.5"; break;
case "3": colRatio = "0.33"; break;
case "4": colRatio = "0.25"; break;
}
markupBuffer.AppendFormat("<ext:FormPanel ID=\"{0}\" Icon=\"PhoneAdd\" Border=\"true\" Collapsible=\"true\" runat=\"server\" Title=\"{1}\" AutoHeight=\"true\" LabelWidth=\"120\">\r\n\t<Items>\r\n\t", panelId, panelTitle);
markupBuffer.AppendFormat("<ext:TableLayout runat=\"server\" ColumnWidth=\"{0}\" Columns=\"{1}\"><Cells>", colRatio, colSetting);
BuildFieldCells(markupBuffer, section.Descendants("Field"));
markupBuffer.Append("</Cells> </ext:TableLayout>");
markupBuffer.Append("</Items>\r\n\t</ext:FormPanel>\r\n\t");
sectionCounter++;
}
}
return markupBuffer.ToString();
}
private void BuildFieldCells(StringBuilder builder, IEnumerable<XElement> elements)
{
foreach (var node in elements)
{
string controlRef = node.Attributes("TextControlID").First().Value;
string prefix = controlRef.Substring(0, 3).ToLower();
bool spansMultiple = (prefix == "tbl");
builder.Append(spansMultiple ? "<ext:Cell ColSpan=\"2\">" : "<ext:Cell>");
bool isNumeric = node.Attribute("MaximumValue") != null || node.Attribute("MinimumValue") != null;
if (isNumeric)
{
builder.AppendFormat("<ext:NumberField ID=\"{0}\" runat=\"server\" />", controlRef);
}
else
{
switch (prefix)
{
case "txt":
string lowerId = controlRef.ToLower();
bool isDate = lowerId.Contains("date") || lowerId.Contains("time") || lowerId.Contains("deadline") || lowerId.Contains("birthday");
builder.AppendFormat(isDate ? "<ext:DateField ID=\"{0}\" runat=\"server\" />" : "<ext:TextField ID=\"{0}\" runat=\"server\" />", controlRef);
break;
case "ddl":
builder.AppendFormat("<ext:ComboBox Editable=\"false\" ID=\"{0}\" runat=\"server\" />", controlRef);
break;
case "cbl":
builder.AppendFormat("<ext:CheckboxGroup ID=\"{0}\" runat=\"server\" ><Items><ext:Checkbox runat=\"server\" BoxLabel=\"Option A\" /> </Items></ext:CheckboxGroup>", controlRef);
break;
case "rbl":
builder.AppendFormat("<ext:RadioGroup ID=\"{0}\" runat=\"server\" ><Items><ext:Radio runat=\"server\" BoxLabel=\"Option A\" /> </Items></ext:RadioGroup>", controlRef);
break;
case "tbl":
builder.AppendFormat("<ext:FormPanel Border=\"false\" IsFormField=\"true\" ID=\"{0}\" runat=\"server\" ><Items><ext:DisplayField runat=\"server\" Text=\"--Dynamic Block--\" /></Items></ext:FormPanel>", controlRef);
break;
case "chk":
builder.AppendFormat("<ext:Checkbox ID=\"{0}\" runat=\"server\" />", controlRef);
break;
case "rdo":
builder.AppendFormat("<ext:Radio ID=\"{0}\" runat=\"server\" />", controlRef);
break;
default:
Response.WriteLine("Warning: Unknown control prefix encountered: " + prefix);
return;
}
}
builder.Append("</ext:Cell>\r\n\t");
}
}
</script>
Client-Side Integration and Validation Hendling
The generated output incorporates a standard ResourceManager alongside JavaScript routines that manage form validation states. The script monitors input validity, toggles the enabled state of action buttons, and triggers contextual notifications when the validation status changes. The layout structure also embeds a collapsible approval tracking panel and synchronized toolbars for data submission and persistence.
<%%@ Control Language="C#" AutoEventWireup="true" CodeBehind="<%=TargetControlName%>.ascx.cs"
Inherits="NBShop.UserControls.Form.<%=TargetControlName%>" EnableViewState="true" %>
<%%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<script type="text/javascript">
var activeValidationState = '';
function triggerNotification(title, message, cssClass) {
if (activeValidationState !== cssClass) {
activeValidationState = cssClass;
Ext.net.Notification.show({
hideFx: { fxName: 'switchOff', args: [{}] },
showFx: { fxName: 'frame', args: ['C3DAF9', 1, { duration: 2.0 }] },
iconCls: cssClass,
closeVisible: true,
html: message,
title: title + ' ' + new Date().format('g:i:s A')
});
}
}
</script>
<center>
<div style="width: 830px; text-align: left;">
<ext:FormPanel ID="MainFormContainer" Collapsible="true" Header="false" Icon="PageAdd" runat="server"
MonitorValid="true" Padding="5" ButtonAlign="Right" Width="830px" Layout="Form">
<Items>
<%=GenerateLayoutHtml()%>
<ext:Panel ID="ApprovalHistoryPanel" runat="server" Collapsible="true" Header="true" Icon="UserFemale" Border="true"
Title="Approval History" Height="200">
<AutoLoad Url="/FormServerTemplates/ExamineList.aspx" NoCache="true" Mode="IFrame" ShowMask="true" />
<Listeners>
<Expand Handler="this.reload();" />
<Collapse Handler="this.clearContent();" />
</Listeners>
</ext:Panel>
</Items>
<Buttons>
<ext:Button ID="btnSave" runat="server" Text="Save" CausesValidation="true" Icon="Disk">
<DirectEvents><Click OnEvent="btnSave_Click" Single="true"><EventMask ShowMask="true" Msg="Processing..." /></Click></DirectEvents>
</ext:Button>
<ext:Button ID="btnSubmit" runat="server" Text="Submit" CausesValidation="true" Icon="PageAdd">
<DirectEvents><Click OnEvent="btnSubmit_Click" Single="true"><EventMask ShowMask="true" Msg="Processing..." /></Click></DirectEvents>
</ext:Button>
</Buttons>
<TopBar>
<ext:Toolbar ID="Toolbar1" runat="server">
<Items>
<ext:ToolbarFill ID="ToolbarFill1" runat="server"/>
<ext:Button ID="tbSave" runat="server" Icon="Disk" CausesValidation="true" Text="Save">
<DirectEvents><Click OnEvent="btnSave_Click" Single="true"><EventMask ShowMask="true" Msg="Saving data..." /></Click></DirectEvents>
</ext:Button>
<ext:Button ID="btnSubmitTop" runat="server" Icon="PageAdd" CausesValidation="true" Text="Submit">
<DirectEvents><Click OnEvent="btnSubmit_Click" Single="true"><EventMask ShowMask="true" Msg="Processing..." /></Click></DirectEvents>
</ext:Button>
</Items>
</ext:Toolbar>
</TopBar>
<BottomBar><ext:StatusBar ID="StatusBar1" runat="server" /></BottomBar>
<Listeners>
<ClientValidation Handler="#{btnSave}.setDisabled(!valid);#{tbSave}.setDisabled(!valid);#{btnSubmit}.setDisabled(!valid);#{btnSubmitTop}.setDisabled(!valid);var state=valid ? 'valaccept' : 'valexclamation';var statusMsg=valid ? '<span style=\'color:green;\'>Validation successful. Ready to submit.</span>' : '<span style=\'color:red;\'>Invalid entries detected. Please review highlighted fields.</span>';this.getBottomToolbar().setStatus({text :statusMsg, iconCls: state});triggerNotification('Form Status',statusMsg,state);" />
</Listeners>
</ext:FormPanel>
</div>
</center>
<script type="text/javascript">
Ext.onReady(function () {
$(function(){
setTimeout(applyVisualAdjustments, 300);
});
top.Ext.getCmp('frmStatesRequestList').maximize();
});
function applyVisualAdjustments() {
$("label.x-form-item-label").addClass("labelStyle");
$("table.x-table-layout").attr("width", "100%");
}
</script>