真正动态且可重用的DataGrids






4.82/5 (26投票s)
如何使用 ITemplate、XML 和模板控件动态添加 DataGrid 列。
引言
DataGrid 为 Web 开发人员提供了无数以表格格式显示数据的选项。尽管它提供了多种显示数据的方式,但它本身并不促进代码和格式的重用。
本文及随附的源代码演示了一个名为 GridHelper
的辅助类的用法,该类使用 XML、模板文件和 ITemplate
接口动态构建 Datagrids。主要目标是提供一个可以跨多个站点使用自定义模板层次结构重用的类。它还允许开发人员在运行时添加格式化的列到网格,而无需重新编译。
背景
没有什么比在多个站点创建外观和感觉相同的 DataGrids 更让我烦恼的了。我看到了创建一种机制的需求,该机制通过消除手动创建 Datagrids 的需要来拥抱我的懒惰。虽然 CSS 有助于列的格式化,但它不允许您合并两行数据以创建一个列,或者跨多个页面和多个网格来格式化特定数据类型。
使用代码
这个项目主要包含三个部分
TemplateLoader
– 继承自Page
对象并隐藏LoadTemplate
方法的对象,用于从文件加载和缓存模板。GridFormats
– 存储来自 Web.Config 文件的网格列。它使用自定义ConfigurationSectionHandler
对象序列化到此对象中。GridHelper
– 使用TemplateLoader
和GridFormats
类执行网格列的动态生成。
TemplateLoader
此类用于从 .ascx 文件加载模板。LoadTemplate
是 Page
和 UserControl
对象可用的继承方法。TemplateLoader
是从文件加载模板的集中式处理程序。
#region TemplateLoader
public class TemplateLoader: System.Web.UI.Page
{
private const string CACHE_HEADER =
"GridHelper.Templates.";
public new ITemplate LoadTemplate(string templateName)
{
ITemplate Template = null;
string CachedTemplate =
string.Concat(CACHE_HEADER,templateName);
Template =
this.Context.Cache.Get(CachedTemplate) as ITemplate;
if (Template == null)
{
Template = base.LoadTemplate(templateName);
this.Context.Cache.Insert(CachedTemplate, Template,
new System.Web.Caching.CacheDependency(Server.MapPath(templateName)),
System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromHours (1),
System.Web.Caching.CacheItemPriority.AboveNormal, null);
}
return Template;
}
}
#endregion
该方法被重写以启用模板对象的缓存。如果文件被更改,它将从缓存中失效并在下一次请求时重新加载。
Gridformats
GridFormats
仅仅是我们的网格列的容器类。进入 GridFormats
类的主入口是 gridname
属性,因此您必须在 Web.Config 文件中有唯一的 gridname
。Column
和 Grid
对象使用 XmlSerialization
和自定义 ConfigurationSectionHandler
对象序列化到 GridFormats
类中。
#region Column
[XmlRoot("Column")]
public class Column
{
private string _Type = string.Empty;
private string _Path = string.Empty;
private string _HeaderText = string.Empty;
private string _DataField = string.Empty;
private bool _Wrap = false;
[XmlAttributeAttribute("datafield")]
public string DataField
{
get
{
return _DataField;
}
set
{
_DataField = value;
}
}
[XmlAttributeAttribute("type")]
public string Type
{
get
{
return _Type;
}
set
{
_Type = value;
}
}
[XmlAttributeAttribute("path")]
public string Path
{
get
{
return this._Path;
}
set
{
this._Path = value;
}
}
[XmlAttributeAttribute("headertext")]
public string HeaderText
{
get
{
return _HeaderText;
}
set
{
this._HeaderText = value;
}
}
[XmlAttributeAttribute("wrap")]
public bool Wrap
{
get
{
return this._Wrap;
}
set
{
this._Wrap = value;
}
}
}
#endregion
#region Grid
[Serializable()]
public class Grid
{
private Column[] _Columns;
private string _Name = string.Empty;
[XmlAttributeAttribute("name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
[XmlArray("Columns"), XmlArrayItem("Column")]
public Column[] Columns
{
get
{
return _Columns;
}
set
{
_Columns= value;
}
}
}
#endregion
#region GridFormats
[SerializableAttribute()]
public class GridFormats :
System.Collections.Specialized.NameObjectCollectionBase,
IXmlSerializable
{
public Grid this[int index]
{
get
{
return base.BaseGet(index) as Grid;
}
set
{
this.BaseSet(index, value);
}
}
public Grid this[string index]
{
get
{
return (base.BaseGet(index) as Grid);
}
set
{
this.BaseSet(index, value);
}
}
public void Add(Grid Item)
{
base.BaseAdd(Item.Name, Item);
}
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer l_Serializer = null;
l_Serializer = new XmlSerializer(typeof(Grid));
reader.ReadStartElement();
while ((reader.NodeType != System.Xml.XmlNodeType.EndElement &&
reader.NodeType != System.Xml.XmlNodeType.None))
{
Grid l_Grid;
l_Grid = ((Grid)(l_Serializer.Deserialize(reader)));
Add(l_Grid);
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer l_Serializer = null;
l_Serializer = new XmlSerializer(typeof(Grid));
foreach (string l_strKey in base.Keys)
{
Grid l_objServer = this[l_strKey];
l_Serializer.Serialize(writer, l_objServer);
}
}
}
#endregion
这是包含 GridFormats
对象层次结构**原始** XML 的 Web.Config 条目。
<GridFormats
assembly="DynamicDataGrids,Version=2.2.0.0,
Culture=neutral,PublicKeyToken=null"
type="DynamicDataGrids.GridFormats">
<Grid name="Employees">
<Columns>
<Column type="template"
path="~/GridTemplates/LastNameFirstNameTemplate.ascx"
headertext="Name" wrap="false"></Column>
<Column type="bound" headertext="Title"
datafield="Title" wrap="false"></Column>
<Column type="template"
path="~/GridTemplates/DateTemplate.ascx"
headertext="B-Day" wrap="false"></Column>
</Columns>
</Grid>
<Grid name="Products">
<Columns>
</Columns>
</Grid>
<Grid name="Customers">
<Columns>
</Columns>
</Grid>
</GridFormats>
每列都定义在 Columns
标签内,并包含以下属性
type
:确定要创建的列的类型。在此示例中,类型 **不** 定义序列化对象,即代码中没有模板列对象。如果需要,您可以将其添加到代码中,并创建继承的Column
对象,为每种类型的列定义唯一的属性。headertext
:列的标题文本。path
:仅用于模板列,此定义了模板文件的路径。您会在示例中注意到一个波浪号“~”标记。未能将此添加到模板路径将导致项目中出现无效路径异常。datafield
:用于绑定列,此字段确定要绑定到传入数据中的哪个字段。wrap
:用于打开/关闭列中的换行。
您可以在需要创建唯一列的 XML/类中添加每个可能的标签和匹配的属性(CSS 等)。
模板示例
下面是一个模板示例,它接收传入数据源中的两列,并将它们连接起来创建“姓氏,名字”链接。请注意,此类 **没有** 后台代码。OnDataBinding
事件调用 BindData
方法,如果姓氏为空,该方法会隐藏链接。
<%@ Control Language="C#" %>
<asp:LinkButton id=lnkLastFirstName runat="server"
Text=<%# string.Format("{0}, {1}",
DataBinder.Eval(Container, "DataItem.LastName" ),
DataBinder.Eval(Container, "DataItem.FirstName" ))%>
OnDataBinding=BindData ></asp:LinkButton>
<script runat="server">
void BindData(Object sender, EventArgs e)
{
System.Web.UI.WebControls.DataGridItem container =
(System.Web.UI.WebControls.DataGridItem) this.NamingContainer;
string LastName =
DataBinder.GetPropertyValue(container.DataItem,
"LastName", string.Empty);
if (LastName == string.Empty)
{
LinkButton Link = (LinkButton) sender;
Link.Visible = false;
}
}
</script>
GridHelper
Gridhelper
利用 TemplateLoader
和 GridFormats
对象来生成列并将其附加到网格中。此类 **非常** 简单,可以根据您的需求进行修改/扩展。
性能
如果处于高流量环境中,这始终是一个主要问题。缓存模板可以提高性能,但使用此方法的主要开销来自 DataBinder.Eval
。好消息是这可以避免!对于我提供的所有示例,您可以更改代码以直接强制转换为您的对象类型(DataView
、自定义对象等),并避免 DataBinder
反射开销。我没有对 GridHelper
类进行详细的性能分析,但我相信此类中的性能将与使用自定义代码在运行时向网格添加列的性能相似。如果有人发现任何重大的性能问题,请立即告知我,我将立即解决。
历史
在我**完整的**源代码中,我确实能够通过反射调用代码中定义的 ITemplate
列并将其添加到网格中。一旦我清理了那部分代码,我将在这里发布它,并提供模板列的**完整**实现。当然,只有在其他人需要的情况下才会这样做,否则我将开始我的新项目。:)