在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函数检索任何必要的上下文数据。
很想听听大家可能提出的改进意见。还没有测试性能,但看看这些模板与标记中的内联模板相比如何,也会很有趣。