浅谈ASP.NET MVC中的FluentHtml与连续接口

2013 年 8 月 14 日3820

  我们力求页面层代码简洁并具有较好的可读性,在ASP.NET MVC的平台上,我们以新的起点来实现这一目标.MvcContrib.FluentHtml和Spark ViewEngine给我们做出了榜样.本文将以MvcContrib.FluentHtml为例探究它的实现机制:Fluent Interface.

  在MvcContrib.FluentHtml的应用中,我们随处可以见到下面的代码:

  < %= this.TextBox(x => x.Person.Name).Title("Enter the person's name").Label("Name:") %> …… < %= this.Select(x => x.Person.Gender).Options(Model.Genders).Size(5).Label("Gender:") .Title("Select the person's gender") %> 浏览器中生成的代码为:

  < LABEL id=Person_Name_Label for=Person_Name>Name:< /LABEL> < INPUT id=Person_Name title="Enter the person's name" value=Jeremy maxLength=50 name=Person.Name> . < SELECT id=Person_Gender title="Select the person's gender" size=5 name=Person.Gender>< OPTION selected value=M>Male< /OPTION>< OPTION value=F>Female< /OPTION>< /SELECT>

  上面对动态生成TextBox和Select的代码很有意思,我们使用普通的方式在页面上生成同样的客户端代码,CS代码大致是这样的:

  Label label = new Label();

  label.Text = "Name";

  TextBox textbox= new TextBox();

  textbox.ToolTip ="Enter the person's name";

  textbox.ID = "No.10001";

  textbox.ID = "Person.Name";

  而FluentHtml创建页面元素的方式让我们很容易联想到StringBuilder的使用:

  StringBuilder stringbuilder = new StringBuilder(); stringbuilder.Append("Hello").Append(" ").Append("World!");

  Fulent Interface

  这种实现编程方式就是"Fluent Interface",这并不是什么新概念,2005年Eric Evans 和Martin Fowler就为这种实现方式命名.源文档 可以通过维基百科中对Fluent Interface的描述获得一个基本的了解:

  In software engineering, a fluent interface (as first coined by Eric Evans and Martin Fowler) is a way of implementing an object oriented API in a way that aims to provide for more readable code.

  我们分解上面的话:

  它是面向对象API的一种实现方式,目的是增加代码的可读性.。既然我们最熟悉的是StringBuilder,我们就从这个线索追下去:打开Reflector,很容易找到StringBuilder的Append方法:

  public StringBuilder Append(string value)

  { if (value != null)

  { string stringValue = this.m_StringValue;

  IntPtr currentThread = Thread.InternalGetCurrentThread();

  if (this.m_currentThread != currentThread)

  {

  stringstringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity);

  } int length = stringValue.Length;

  int requiredLength = length + value.Length;

  if (this.NeedsAllocation(stringValue, requiredLength))

  { string newString = this.GetNewString(stringValue, requiredLength);

  newString.AppendInPlace(value, length);

  this.ReplaceString(currentThread, newString);

  }

  else

  {

  stringValue.AppendInPlace(value, length);

  this.ReplaceString(currentThread, stringValue);

  }

  } return this;

  }

  阅读这段有两个特别要注意的点:1.方法的返回值是StringBuilder类型 2.最后一句:return this; 为了深刻理解,我们写一个简单的StringBuilder:

  public interface IContentBuilder {

  void WriteContent();

  IContentBuilder Append(string partialContent);

  } public class TestContentBuilder : IContentBuilder { string temp;

  #region IContentBuilder Members void IContentBuilder.WriteContent() {

  Console.Write(temp);

  }

  IContentBuilder IContentBuilder.Append(string partialContent) {

  temp += partialContent;

  return this;

  } #endregion }

  … … //调用代码 IContentBuilder t = new TestContentBuilder(); t.Append("test").Append("Hello").WriteContent();

  跑一下代码,和StringBuilder效果是一样的.从上面的应用也可以看出:Fluent Interface经常用来完成对象的构造和属性赋值.

  言归正传:FluentHTML了解了Fluent Interface,我们来看一下MVCContrib.FluentHTML的实现,

  这里以TextBox为例进行考察,首先看一下它的继承关系:

  public class TextBox : TextInput public abstract class TextInput : Input, ISupportsMaxLength where T : TextInput public abstract class Input : FormElement where T : Input, Ielement 泛型是一种高层次的算法抽象,我们就通过Input一窥端倪:

  public abstract class Input : FormElement where T : Input, IElement {

  protected object elementValue;

  protected Input(string type, string name) : base(HtmlTag.Input, name)

  {

  builder.MergeAttribute(HtmlAttribute.Type, type, true);

  } protected Input(string type, string name, MemberExpression forMember, IEnumerable behaviors) : base(HtmlTag.Input, name, forMember, behaviors)

  {

  builder.MergeAttribute(HtmlAttribute.Type, type, true);

  } /// /// Set the 'value' attribute. /// /// The value for the attribute.

  public virtual T Value(object value)

  { elementValue = value;

  return (T)this;

  }

  /// /// Set the 'size' attribute. /// /// The value for the attribute.

  public virtual T Size(int value)

  { Attr(HtmlAttribute.Size, value);

  return (T)this; } protected override void PreRender() { Attr(HtmlAttribute.Value, elementValue);

  base.PreRender();

  }

  }

  以 Size方法为例,可以看出这是一种典型的Fluent Interface实现: public virtual T Size(int value) { Attr(HtmlAttribute.Size, value); return (T)this; } 分析到这里,上面的语句中还有一点比较奇怪,就是Lambda表达式的部分:

  this.TextBox(x => x.Person.Name).Title("Enter the person's name").Label("Name:") TextBox的实现代码里面我们没有看到对Lambda表达式的支持.那是在什么地方完成的呢?通过跟进,我们来到了 ViewDataContainerExtensions,它是IViewDataCon

  namespace MvcContrib.FluentHtml {

  /// /// Extensions to IViewDataContainer ///

  public static class ViewDataContainerExtensions

  {

  /// /// Generate an HTML input element of type 'text' and set its value from ViewData based on the name provided. /// /// The view.

  /// Value of the 'name' attribute of the element.Also used to derive the 'id' attribute.

  public static TextBox TextBox(this IViewDataContainer view, string name)

  {

  return new TextBox(name).Value(view.ViewData.Eval(name));

  } … … tainer 的Extension Method:

  看一下return new TextBox(name).Value(view.ViewData.Eval(name)); 所以这里就成了TextBox定义方法链的第一步.

  FluentHtml与连续接口总结

  为了能够在View中能够简洁清晰的构造HTML元素,Asp.net MVC中通过htmlHelper.InputHelper来实现页面元素的构造. 页面层所使用的,HTML也是htmlHelper的Extension Method.相比较起来,htmlHelper提供了基础的页面控件定义和构造,而FluentHTML表现的更为灵活.除了FluentHTML,著名的Spark View Engine也有类似的实现,大家可以关注一下.

网友评论

欢迎发表评论

0 0