ASP.NET MVC3 的组件模型
在本文中,我将向您展示如何使用 Builder 和 Factory 设计模式为 ASP.NET MVC3 构建一个组件模型。您可以使用此模型编写强类型组件来绑定数据、处理分层对象以及为 ASP.NET MVC3 编写 jQuery UI 控件/HTML5 控件。
概述
在本文中,我将向您展示如何使用 Builder 和 Factory 设计模式为 ASP.NET MVC3 构建一个组件模型。您可以使用此模型编写强类型组件来绑定数据、处理分层对象以及为 ASP.NET MVC3 编写 jQuery UI 控件/HTML5 控件。
背景
我们知道,在传统的 ASP.NET 中,我们可以通过使用 ASP.NET 控件模型来为我们的 Web 应用程序编写控件。今天在 MVC3 中,我们通常编写助手(helpers)而不是控件。我认为助手方法对于一些简单的使用场景是一个不错的选择,但并非适用于所有情况。对于渲染单个标签、图像或脚本,助手方法效果很好。我很难想象如何使用助手方法来开发更复杂的控件?如何编写模板容器控件?如何编写可数据绑定的树形视图和菜单?如何轻松地编写和使用这些复杂的控件?事实上,我喜欢助手方法,但同时也憎恨它们。因为助手方法是输出视图中标签的便捷方式,但这并非面向对象的方式!助手方法易于使用,但难以扩展和维护。您如何阅读、记住和维护一个拥有 10 多个重载版本的函数?简直是地狱!所以我们需要另辟蹊径!一种易于学习、更具可读性、面向对象、可扩展且对我们的控件开发熟悉的途径。
开始之前
有一些概念您需要了解
- ASP.NET MVC: http://msdn.microsoft.com/en-us/library/dd381412(VS.98).aspx
- 创建自定义 HTML 助手: http://www.asp.net/mvc/tutorials/creating-custom-html-helpers-cs
- Razor 语法: http://www.asp.net/webmatrix/tutorials/2-introduction-to-asp-net-web-programming-using-the-razor-syntax
- Builder 设计模式: http://www.dotnetage.com/publishing/home/2011/06/17/6859/builder.html
- Factory Method 设计模式: http://www.dotnetage.com/publishing/home/2011/06/17/6857/factory-method.html
目标
我们需要一个 ASP.NET MVC 的组件模型。
- 支持 Razor 和 Web Form 语法
- 提供接口或抽象类来实现
- 支持控件模板
- 支持复合组件
- 支持数据绑定
- 易于与客户端能力(JavaScript jQuery)集成
用法设计
MVC3 与传统 ASP.NET 最大的区别在于 MVC3 直接使用视图和助手来渲染 HTML 标签,而不是像传统 ASP.NET 那样使用任何组件模型。因此,在设计组件模型之前,我们需要了解在 MVC3 中编写控件的各种方式,然后我们还需要了解这些控件的通用行为、属性和用法。现在让我们回顾一下 MVC3 并进行一些测试。
用法 1:扩展方法
我们调用的“Html.XXX()
”和“Ajax.XXX()
”MVC 助手或扩展,实际上是 static
扩展方法,它们是向现有类注入新方法的绝佳方式。当您使用 Razor 时,它提供了一种使用 @helper
编写助手方法的新语法。我将常用内置助手列在下面(注意:每个方法都有 5 个以上的重载版本)
Html.Display()
Html.DisplayFor()
Html.Label()
Html.LabelFor()
Html.LablelForModel()
Html.ActionLink()
Html.RoutLink()
Html.Editor()
Html.EditorFor()
Html.EditorForModel()
Html.CheckBox()
Html.CheckBoxFor()
Html.Hidden()
Html.Password()
Html.PasswordFor()
Html.RadioButton()
Html.RadioButtonFor()
Html.TextBox()
Html.TextBoxFor()
Html.ListBox()
Html.ListBoxFor()
Html.DropDownList()
Html.DropDownListFor()
Html.TextArea()
Html.TextAreaFor()
此列表中的方法总数超过 100 个,并带有重载版本!请注意,这些助手仅用于渲染 <input> <a> <select> <textArea>
标签,可能还会调用表单。为什么助手有这么多重载?!我认为这只是为了“DIY”和“简洁”。虽然助手方法更简单易用,但正如我们所见,它们会带来大量的重载。它们变得更难读、更难记、更难维护。
结论:助手方法应用于没有太多选项的简单控件。这不是面向对象的方式。
用法 2:模板
在传统的 ASP.NET 中,我们通常会在容器控件中使用模板,这非常棒!但在 MVC 中我们该如何做呢?我在 ASP.NET 官方网站上找不到任何解决方案,只能编写助手方法或视图文件。如何编写支持模板功能的强类型控件?例如,如何在 MVC3 中编写一个 Panel 控件?即使对于 form 标签,MVC 也提供了两个奇怪的方法“BeginForm
”、“EndForm
”来实现它。为什么不提供类似这样的方法呢?
Razor
@{
Html.Form()
.Body(@<text>
<p>Here is form body</p>
</text>)
.End();
}
Web Form
<%
Html.Form()
.Body(()=>{%>
<p>Here is form body</p>
<%})
.End();
%>
这就是我想要的。正如您所见,Web Form 和 Razor 都实现了模板。在 Web Form 语法中,我们应该使用 Action 委托来嵌入其他 HTML 标签。对于 Razor 视图引擎,它只允许使用内联块文本 @<text></text>
,在上面的示例中,我使用“Builder”设计模式充当 M-V-C 模式的控件角色。
好的,我找到了实现控件模板的方法。这对于任何容器控件都非常有用的。
用法 3:复合控件
在传统 ASP.NET 的时代(在我那个时代,我是一个老人,您懂的 ^^),我记得我经常为我的复杂控件实现 CompositeControl
基类。显然,当我们的控件变得越来越复杂时,我们需要为它们提供更多的选项来控制它们的行为,仅使用助手方法是不够的。我们使用组件模型来实现复合控件,例如 TreeView
、Grid
、ComobBox
、ListBox
、Menu
、SiteMap
、Toolbar
等等。这些控件是我们随时随地会使用的常用控件。
所以,让我们想象一下如何在我们的 MVC 项目中使用这些“控件”
@{
Ajax.DJME().ComboBox()
.Width(200)
.Items(items=>{
items.Add("text1","value1");
items.Add("text1","value1");
items.Add().Template(@<text><a href="#">
I am template item.</a></text>);
})
.Render();
}
@{
Ajax.DJME().TreeView()
.Nodes(nodes=>{
nodes.Add("node1","http://domain.com");
nodes.Add("node2","http://domain.com");
nodes.Add("node3","http://domain.com")
.Nodes(children=>{
children.Add("subNode1","http://sub.domain.com");
});
})
}
复合用法与模板用法非常相似。它也使用 Action 委托来实现。
用法 4:数据绑定
在 MVC 方式中,我们会在 Controller 端生成 Model 实例,并将其通过 ViewData.Model
传递到 View 端。因此,我们的组件只在 View 端运行,我们不应该关心如何生成 Model,它只是控件的一个参数。数据绑定只是复合用法的一种自动化版本。数据绑定用法可能如下所示
@{
Ajax.DJME().ListBox()
.Bind(Model)
.Render();
}
@{
Ajax.DJME().Grid(Model)
.AutoGenerateColumns()
.Sortable()
.Pagable()
.Render();
}
组件模型:使用 Builder 和 Factory Method 设计模式实现 M-V-C 模式
我们之前讨论了 MVC 的多种书写方式,接下来我们将深入探讨如何构建一个抽象级别的对象模型和实现。
首先,在讨论对象模型设计之前,您需要记住 ASP.NET MVC 只是一个框架或一套开发工具,实际上 M-V-C 是一个非常经典的设计模式。因此,我们的组件模型将实现 MVC 模式。现在让我们看看抽象级别对象的类图。

模型
我们不应该关心如何生成 Model。对于组件模型,只需将其用作外部数据源。
视图
ViewComponent
基类充当 View,用于保存属性值并渲染 HTML 内容。子类重写 RenderXXX()
方法以将 HTML 内容写入 View 响应。ViewComponent
类似于传统的 ASP.NET 控件基类。以下代码是用 C# 编写的 ViewComponent
。
public abstract class ViewComponent
{
private ViewContext _viewContext;
private string _id;
public ViewContext ViewContext
{
get { return _viewContext; }
}
public ViewComponent(ViewContext viewContext)
{
this._viewContext = viewContext;
}
public virtual string Name { get; set; }
public virtual string ClientID
{
get
{
if ((string.IsNullOrEmpty(_id)) && (!string.IsNullOrEmpty(Name)))
{
var _tag = new TagBuilder(TagName);
_tag.GenerateId(Name);
if (_tag.Attributes.ContainsKey("id"))
_id = _tag.Attributes["id"];
else
_id = Name;
}
return _id;
}
private set { _id = value; }
}
public virtual string TagName
{
get
{
return "div";
}
}
public virtual void Render(HtmlTextWriter writer)
{
RenderBeginContent(writer);
RenderConent(writer);
RenderEndConent(writer);
}
public virtual void RenderBeginContent(HtmlTextWriter writer)
{
TagBuilder _tag = new TagBuilder(TagName);
if (!string.IsNullOrEmpty(Name))
{
_tag.GenerateId(Name);
if (!_tag.Attributes.ContainsKey("id"))
_tag.MergeAttribute("id", Name);
Id = _tag.Attributes["id"];
if (TagName.Equals("input", StringComparison.OrdinalIgnoreCase) ||
TagName.Equals("textarea", StringComparison.OrdinalIgnoreCase))
_tag.MergeAttribute("name", Name);
}
writer.Write(_tag.ToString(TagRenderMode.StartTag));
}
public virtual void RenderConent(HtmlTextWriter writer) { }
public virtual void RenderEndTag(HtmlTextWriter writer)
{
writer.WriteEndTag(TagName);
}
public string GetHtml()
{
var result = new StringBuilder();
using (var writer = new HtmlTextWriter(new StringWriter(result)))
{
Render(writer);
}
return result.ToString();
}
}
Control
然后 ViewComponentBuilder
充当 MVC 模式的控件角色,并实现了 Builder 设计模式。它控制 ViewComponent
的构建过程。
public class ViewComponentBuilder<TComponent,TBuilder>
where TComponent : ViewComponent
where TBuilder : ViewComponentBuilder<TComponent, TBuilder>
{
public ViewComponentBuilder(TComponent component)
{
this.Component = component;
}
private TComponent component;
public TComponent Component
{
get { return component; }
private set { component = value; }
}
public ViewContext ViewContext
{
get
{
return component.ViewContext;
}
}
public TBuilder GenerateId()
{
if (string.IsNullOrEmpty(Component.Name))
{
string prefix = Component.GetType().Name;
string key = "DJME_IDSEQ_" + prefix;
int seq = 1;
if (ViewContext.HttpContext.Items.Contains(key))
{
seq = (int)ViewContext.HttpContext.Items[key] + 1;
ViewContext.HttpContext.Items[key] = seq;
}
else
ViewContext.HttpContext.Items.Add(key, seq);
Component.Name = prefix + seq.ToString();
}
return this as TBuilder;
}
public virtual TBuilder Name(string name)
{
Component.Name = name;
return this as TBuilder;
}
public virtual void Render()
{
RenderComponent();
}
protected void RenderComponent()
{
using (var writer = new HtmlTextWriter(ViewContext.Writer))
{
Component.Render(writer);
}
}
public virtual MvcHtmlString GetHtml()
{
return MvcHtmlString.Create(Component.GetHtml());
}
}
在客户端,开发者不会直接使用 ViewComponent
,而是使用 ViewComponentBuilder
。为了使用 builder 并内联编写代码,builder 的每个控件方法都应该返回自身。在完成构建过程后,我们需要调用 Render()
或 GetHtml()
方法来获取构建结果。
以下代码是 ViewComponentBuilder
的用法
@{
Ajax.YourControl()
.Name("Your control name")
.Render();
}
为什么 ViewComponentBuilder
有 Render()
和 GetHtml()
结果获取方法?这是考虑到 WebForm 和 Razor 的 ViewEngines 输出机制。以下列表是对这些方法的解释
Render
- 由ViewEngine
调用,并将ViewComponent
的输出结果写入响应。它将惰性调用并返回空。GetHtml
- 当调用时,它将返回ViewComponent
的输出结果作为MvcHtmlString
。
Factory Method 和 Helper Method
最后,我们需要一个类来构造 ViewComponent
和 ViewCompoentBuilder
并将它们协同工作。我们还需要返回 builder 并将构造方法注入 HtmlHelper
或 AjaxHelper
。现在我们将使用 Factory method 设计模式来实现它。
#1. 创建一个扩展方法并注入到 HtmlHelper
,使 Html.Demo()
能够返回一个 ComponentFactory
实例。
public static class Extensions
{
public static SimpleFactory Demo(this HtmlHelper helper)
{
return new SimpleFactory (helper.ViewContext);
}
}
#2. 创建一个包含 ViewComponentBuilder
构造方法的组件工厂。
public class SimpleFactory
{
private ViewContext _viewContext;
public ViewContext ViewContext
{
get { return _viewContext; }
}
public SimpleFactory (ViewContext viewContext)
{
this._viewContext = viewContext;
}
public TextViewComponentBuilder Text()
{
return new TextViewComponentBuilder
(new TextViewComponent(this.ViewContext)).GenerateId();
}
}
#3. 我们可以这样使用工厂
@Html.Demo().Text().GetHtml()
该工厂不继承任何基类。它只支持 ViewComponentBuilder
的构造。您可以定义各种工厂来分组不同种类的组件方法。工厂方法和 static
扩展方法结合可以带来各种挑战。我们可以使用 abstract
工厂来创建一系列具有相同行为的组件模型。您甚至可以在 static
扩展方法中使用 DI 来动态创建/构造组件工厂。想象一下,您编写了一套 HTML 输入组件,稍后您可以提供一套 Html5 输入或 jQuery 输入组件。只需修改 static
方法中的 abstract
工厂。您可以通过 DI 注入 abstract
工厂的实现。如何动态构造组件是一个生动的话题,我在这里只是提供一个概述。我将在后续的文章中与您分享更多关于这个主题的内容。
教程 1:实现 HTML5 输入控件
现在,让我们使用这个组件模型来创建一个 Html5 输入控件集。
下图是 Html5 输入类的类图。

步骤 1:实现 ViewComponent
创建 Html5InputViewComponent
,继承自 ViewComponent。重写
Render
方法并渲染 input HTML 标签
public class Html5InputViewComponent:ViewComponent
{
public Html5InputViewComponent(ViewContext viewContext) : base(viewContext) { }
private Html5InputTypes inputType = Html5InputTypes.Text;
public Html5InputTypes InputType
{
get { return inputType; }
set { inputType = value; }
}
public object Value { get; set; }
public override void Render(System.Web.UI.HtmlTextWriter writer)
{
TagBuilder input = new TagBuilder("input");
input.GenerateId(this.Name);
if (InputType.Equals(Html5InputTypes.DatetimeLocal))
input.MergeAttribute("type", "datetime-local");
else
input.MergeAttribute("type", inputType.ToString().ToLower());
if (!input.Attributes.ContainsKey("name"))
input.Attributes.Add("name", this.Name);
if (Value != null)
input.MergeAttribute("value", Value.ToString());
writer.Write(input.ToString(TagRenderMode.SelfClosing));
}
}
步骤 2:实现 ViewComponentBuilder
创建一个 Html5InputViewComponentBuilder
来控制 Html5InputViewComponent
。在这种情况下,我们只添加一个“Value
”方法来设置 Html5InputViewComponent.Value
属性。
public class Html5InputViewComponentBuilder :
ViewComponentBuilder<Html5InputViewComponent, Html5InputViewComponentBuilder>
{
public Html5InputViewComponentBuilder(Html5InputViewComponent component) :
base(component) { }
public Html5InputViewComponentBuilder InputType(Html5InputTypes type)
{
Component.InputType = type;
return this;
}
public Html5InputViewComponentBuilder Value(object value)
{
Component.Value=value;
return this;
}
}
步骤 3:创建工厂
Html5ViewComopnentBuilderFactory
类包含一组构造方法。这个工厂提供了一些简化的包装方法,可以预先构建 Html5InputViewComponent
。
public class Html5ViewComponentBuilderFactory
{
private ViewContext _viewContext;
public ViewContext ViewContext
{
get { return _viewContext; }
}
public Html5ViewComponentBuilderFactory(ViewContext viewContext)
{
this._viewContext = viewContext;
}
public Html5InputViewComponentBuilder Input(Html5InputTypes type)
{
return new Html5InputViewComponentBuilder
(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(type);
}
public MvcHtmlString Number(int value=0)
{
return new Html5InputViewComponentBuilder
(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(Html5InputTypes.Number)
.Value(value)
.GetHtml();
}
public MvcHtmlString Range(int value = 0)
{
return new Html5InputViewComponentBuilder
(new Html5InputViewComponent(this.ViewContext))
.Value(value)
.GenerateId()
.InputType(Html5InputTypes.Range)
.GetHtml();
}
public Html5InputViewComponentBuilder Date()
{
return new Html5InputViewComponentBuilder
(new Html5InputViewComponent(this.ViewContext))
.GenerateId()
.InputType(Html5InputTypes.Date);
}
}
步骤 4:编写扩展方法将 Factory 注入 HtmlHelper
public static class Extenstions
{
public static Html5ViewComponentBuilderFactory Html5(this HtmlHelper helper)
{
return new Html5.Html5ViewComponentBuilderFactory(helper.ViewContext);
}
}
步骤 5:在 View 中编写客户端代码
<fieldset>
<legend>Html5 Input components demo</legend>
<p>
Number:
</p>
<p>
@Html.Html5().Number(20)
</p>
<p>
Range:
</p>
<p>
@Html.Html5().Range(5)
</p>
<p>
Date:
</p>
<p>
@Html.Html5().Date()
</p>
</fieldset>
模板化
为了给组件添加模板功能,我定义了一个 IHtmlTemplate
接口。
public interface IHtmlTemplate
{
Action Content { get; set; }
Func<object, object> InlineContent { get; set; }
bool IsEmpty { get; }
void WriteTo(HtmlTextWriter writer);
}
IHtmlTemplate
提供了两个模板属性
Content
– 这个模板属性是一个 Action 委托,允许开发者像这样使用它:Content(()=>{ @*content body*@ }
。它可以支持 WebForm 和 Razor 语法。InlineContent
– 这个模板属性仅适用于 Razor 视图引擎。Razor 识别Func<object,object>
委托并将其渲染为内联文本块@<text>
。
实现 IHtmlTemplate
接口
public class HtmlTemplate : IHtmlTemplate
{
public Action Content { get; set; }
public Func<object, object> InlineContent { get; set; }
public void WriteTo(System.Web.UI.HtmlTextWriter writer)
{
if (Content != null)
{
Content.Invoke();
}
else
{
if (InlineContent != null)
writer.Write(InlineContent(null).ToString());
}
}
public bool IsEmpty
{
get
{
return ((Content == null) && (InlineContent == null));
}
}
}
在接下来的教程中,我将向您展示如何在组件中使用 IHtmlTemplate
和 HtmlTemplate
。
教程 2:容器组件
本教程介绍如何创建一个带有主体模板的 Panel
组件。
步骤 1. 实现 PanelViewComponent
添加一个 PanelViewComponent
,继承自 ViewComponent
。添加一个 Content
属性,并将其类型设置为 HtmlTemplate
(也可以设置为 IHtmlTemplate
)。
public class PanelViewComponent:ViewComponent
{
public PanelViewComponent(ViewContext context) : base(context)
{
Content = new HtmlTemplate();
}
public HtmlTemplate Content { get; set; }
public string Title { get; set; }
public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
writer.WriteFullBeginTag("div");
writer.Write(Title);
writer.WriteEndTag("div");
if (!Content.IsEmpty)
Content.WriteTo(writer);
}
}
步骤 2. 实现 PanelViewComponentBuilder
public class PanelViewComponentBuilder:ViewComponentBuilder
<PanelViewComponent,PanelViewComponentBuilder>
{
public PanelViewComponentBuilder(PanelViewComponent component) :
base(component) { }
public PanelViewComponentBuilder Title(string title)
{
Component.Title = title;
return this;
}
public PanelViewComponentBuilder Body(Action body)
{
if (body != null)
Component.Content.Content = body;
return this;
}
public PanelViewComponentBuilder Body(Func<object, object> body)
{
if (body != null)
Component.Content.InlineContent = body;
return this;
}
}
我们将 Action
和 Func
委托暴露在 Body
方法中供开发者使用。
步骤 3:添加工厂方法
public static class Extenstions
{
public static DNA.Mvc.ComponentModel.Html5.Html5ViewComponentBuilderFactory Html5
(this HtmlHelper helper)
{
return new Html5.Html5ViewComponentBuilderFactory(helper.ViewContext);
}
public static PanelViewComponentBuilder Panel(this HtmlHelper helper)
{
return new PanelViewComponentBuilder
(new PanelViewComponent(helper.ViewContext)).GenerateId();
}
}
步骤 4:在 View 中使用 Panel
@{
Html.Panel()
.Title("Panel title")
.Body(@<text>
<p> Hi here is panel body contents. </p>
</text>)
.Render();
}
复合组件
如上所述,当我们的组件组合了不同的子组件时,它就变成了一个复杂的组件。基本上,ViewComponent
、ViewComponentBuilder
和 IHtmlTemplate
可以实现大多数组件。而我们主要考虑的复杂组件是
- 如何以编程方式构造子组件
- 如何为数据绑定构造子组件
简单来说,复合组件由组件容器和子组件组成。在许多情况下,复合组件由许多不同类型的子组件组成。为了使子组件与容器的用法相同,我们应该在容器 Builder
方法的上下文中传递子组件构建器的构造方法。这可以减少耦合并易于进行更改。
教程 3:LinkList
在本教程中,我将向您展示如何创建一个简单的列表组件。它由一个列表容器(LinkList
)和多个链接列表项组件组成。我将展示如何在 LinkList builder 方法中添加链接列表项。
步骤 1:创建容器组件和子组件
public class LinkListComponent:ViewComponent
{
public LinkListComponent(ViewContext context) : base(context)
{ Links = new List<LinkComopnent>(); }
public virtual ICollection<LinkComopnent> Links { get; set; }
public override string TagName
{
get
{
return "ul";
}
}
public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
foreach (var link in Links)
{
link.Render(writer);
}
}
}
public class LinkListComponentBuilder:ViewComponentBuilder
<LinkListComponent,LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }
public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory=new LinkComponentBuilderFactory(this.Component);
if (items != null)
items.Invoke(factory);
return this;
}
}
public class LinkComopnent:ViewComponent
{
public LinkComopnent(ViewContext context) : base(context) { }
public string Title { get; set; }
public string Url { get; set; }
public override string TagName
{
get
{
return "li";
}
}
public override void RenderConent(System.Web.UI.HtmlTextWriter writer)
{
var a = new TagBuilder("a");
a.MergeAttribute("href", Url);
a.InnerHtml = Title;
writer.Write(a.ToString());
}
}
public class LinkComponentBuilder:ViewComponentBuilder
<LinkComopnent,LinkComponentBuilder>
{
public LinkComponentBuilder(LinkComopnent component) : base(component) { }
public LinkComponentBuilder Title(string title)
{
this.Component.Title = title;
return this;
}
public LinkComponentBuilder Url(string url)
{
Component.Url = url;
return this;
}
}
步骤 2:添加子组件构建器工厂和容器组件构建器
public class LinkComponentBuilderFactory
{
public LinkListComponent ItemContainer { get; set; }
public LinkComponentBuilderFactory(LinkListComponent container)
{
this.ItemContainer = container;
}
public LinkComponentBuilder Add()
{
var item=new LinkComopnent(ItemContainer.ViewContext);
ItemContainer.Links.Add(item);
return new LinkComponentBuilder(item);
}
}
- 创建子组件并添加到容器组件。
- 创建子组件构建器并绑定到子组件。
- 将子组件构建器返回给客户端视图。
public class LinkListComponentBuilder:ViewComponentBuilder
<LinkListComponent,LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }
public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory=new LinkComponentBuilderFactory(this.Component);
if (items != null)
items.Invoke(factory);
return this;
}
}
我们将 LinkComponentBuilderFactory
作为参数通过 Action
委托传递给客户端视图。
步骤 3:在 View 中使用
@{
Html.LinkList()
.Items(items =>
{
items.Add().Title("Item1").Url("#");
items.Add().Title("Item1").Url("#");
items.Add().Title("Item1").Url("#");
})
.Render();
}
现在我们提供了在 View 中手动添加子组件的方法。当我们需要组件能够自行创建子项时,这是另一种用法 - DataBinding
。
我们只做了一些更改,使 LinkListComponentBuilder
能够绑定到数据 Model。
public class LinkListComponentBuilder : ViewComponentBuilder<LinkListComponent,
LinkListComponentBuilder>
{
public LinkListComponentBuilder(LinkListComponent component) : base(component) { }
public LinkListComponentBuilder Items(Action<LinkComponentBuilderFactory> items)
{
var factory = new LinkComponentBuilderFactory(this.Component);
if (items != null)
items.Invoke(factory);
return this;
}
public LinkListComponentBuilder Bind<T, TKey, TValue>(IEnumerable<T> model,
Expression<Func<T, TKey>> keySelector,
Expression<Func<T, TValue>> valueSelector)
where T : class
{
if (model != null)
{
this.Items(items=>{
foreach (var t in model)
{
items.Add()
.Title(keySelector.Compile().Invoke(t) as string)
.Url(valueSelector.Compile().Invoke(t) as string);
}
});
}
return this;
}
}
@{
var listitems = new List<Dna.Demo.Web.Collation>();
listitems.Add(new DNA.Demo.Web.Collation() { Key="BindingItem1",Value="#" });
listitems.Add(new DNA.Demo.Web.Collation() { Key = "BindingItem2", Value = "#" });
listitems.Add(new DNA.Demo.Web.Collation() { Key = "BindingItem3", Value = "#" });
Html.LinkList()
.Bind(listitems, m => m.Key, m => m.Value)
.Render();
}
加法
我使用这个组件模型和 jQuery 客户端能力创建了一个开源项目 DJME - The jQuery MVC Extensions。包括所有 jQuery UI 小部件和许多常用组件,如 TreeView
、Menu
、Toolbar
、SiteMap
、ContextMenu
、DropDownMenu
、Textbox
、ComobBox
、ListBox
、RichTextBox
等等。如果您对 jQuery MVC 扩展感兴趣,可以访问 http://djme.codeplex.com 进行下载。同时,您也可以访问 http://www.dotnetage.com 尝试在线演示。