ASP.NET数据绑定与模板的动态实现

ASP.NETweb开发平台中,开发者经常需要处理数据展示与逻辑层的分离。ASP.NET提供了如WPF等工具,使得这种分离变得更为直观和简单。然而,在某些情况下,ASP.NET的实现方式可能会让一些在PHP或经典ASP中非常简单的任务变得复杂。特别是当需要根据数据本身应用逻辑来动态改变数据展示时,ASP.NET虽然提供了如ListView、Repeater等控件来简化这一过程,但当逻辑变得复杂,或者事先不知道模板的具体样式时,问题就出现了。

在这种情况下,不得不放弃在标记中使用的ItemTemplate模式,转而实现ITemplate或IBindableTemplate接口。这些接口是微软的巧妙设计,但它们在引入标准HTML对象到模板时可能会变得非常困难或冗长。

在最近的一个项目中,遇到了这样的问题,因此编写了一个简单的IBindableTemplate接口实现,它为解决了这个问题。

public class LiteralItemTemplate : IBindableTemplate { private string formatString; public delegate string LambdaExpr(object container); public Dictionary LambdaDictionary = new Dictionary(); public LiteralItemTemplate(string FormatString) { formatString = FormatString; } public void InstantiateIn(Control container) { LiteralControl ctl = new LiteralControl(); ctl.DataBinding += new EventHandler(OnDataBinding); container.Controls.Add(ctl); } public void OnDataBinding(object sender, EventArgs e) { LiteralControl target = (LiteralControl)sender; Control item = target.BindingContainer; target.Text = Regex.Replace(formatString, @"(\{.+?\})", m => { string word = m.Groups[1].Value; int ind = word.IndexOf(":"); return string.Format(string.Format("{0{0}}", (ind > 0) ? word.Substring(ind, word.Length - ind - 1) : string.Empty), evaluateKey(item.DataItem, word.Substring(1, ((ind > 0) ? (ind - 1) : (word.Length - 2))))); }); } private object evaluateKey(object container, string key) { return (LambdaDictionary.ContainsKey(key)) ? LambdaDictionary[key](container) : DataBinder.Eval(container, key); } public System.Collections.Specialized.IOrderedDictionary ExtractValues(Control container) { System.Collections.Specialized.OrderedDictionary _table = new System.Collections.Specialized.OrderedDictionary(); return _table; } }

这个类非常简单。已经实现了IBindableTemplate的三个必要成员中的两个。第三个ExtractValues,对于双向数据绑定是必要的,但当时并不需要。

例如,可以替换在标记中创建的简单ItemTemplate:

<asp:Repeater ID="myDataBoundControl" runat="server"> <ItemTemplate> <tr> <td> <a href='<%# Eval("WebsiteUrl") %>'> <%# Eval("CompanyName") %> </a> </td> <td> <%# Eval("IndustrySector") %> </td> <td> <%# string.Format("{0:#,0}",Eval("MarketCap")) %> </td> </tr> </ItemTemplate> </asp:Repeater>

并用以下方式替换:

protected void Page_Load(object sender, EventArgs e) { Repeater myDataBoundControl = new Repeater(); myDataBoundControl.ItemTemplate = new LiteralItemTemplate( "" + "" + "{IndustrySector}{MarketCap:#,0}"); myDataBoundControl.DataSource = GetDataSource(); myDataBoundControl.DataBind(); }

在这种情况下,使用的语法类似于string.Format()的语法,但略有不同。如果想应用DataBinder.Eval("FieldName"),会在构造函数中传递一个包含{FieldName}的字符串。

此外,可以使用一个冒号前缀的格式字符串,就像在string.Format中一样。例如,string.Format("{0:#,0}", DataBinder.Eval("FieldName"))等同于在构造函数参数中使用格式字符串{FieldName:#,0}。

进一步地,如果想对传入的数据应用某些逻辑,而不是直接显示它,可以非常容易地使用lambda表达式来实现。例如:

protected void Page_Load(object sender, EventArgs e) { Repeater myDataBoundControl = new Repeater(); LiteralItemTemplate t = new LiteralItemTemplate( "" + "" + "{IndustrySector}{LambdaField}"); t.LambdaDictionary.Add("LambdaField", c => BusinessLogicToString(DataBinder.Eval(c, "CompanySize"))); t.LambdaDictionary.Add("LambdaCssClass", c => CompanyIDToCssClass(DataBinder.Eval(c, "CompanyID"))); myDataBoundControl.ItemTemplate = t; myDataBoundControl.DataSource = GetDataSource(); myDataBoundControl.DataBind(); }

lambda表达式接受以下契约:

public delegate string LambdaExpr(object container);

在这里,container是DataBinding容器,可以在lambda表达式中使用DataBinder.Eval函数检索任何必要的上下文数据。

很想听听大家可能提出的改进意见。还没有测试性能,但看看这些模板与标记中的内联模板相比如何,也会很有趣。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485