65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET MVC3 的组件模型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (14投票s)

2011年6月23日

GPL3

11分钟阅读

viewsIcon

70373

downloadIcon

2548

在本文中,我将向您展示如何使用 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 的组件模型。

  • 支持 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 基类。显然,当我们的控件变得越来越复杂时,我们需要为它们提供更多的选项来控制它们的行为,仅使用助手方法是不够的。我们使用组件模型来实现复合控件,例如 TreeViewGridComobBoxListBoxMenuSiteMapToolbar 等等。这些控件是我们随时随地会使用的常用控件。

所以,让我们想象一下如何在我们的 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 模式。现在让我们看看抽象级别对象的类图。

djme_abstraction.png

模型

我们不应该关心如何生成 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 输入类的类图。

Html5Input_Classes.png
步骤 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>

html5_run_result.png

模板化

为了给组件添加模板功能,我定义了一个 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();
} 

复合组件

如上所述,当我们的组件组合了不同的子组件时,它就变成了一个复杂的组件。基本上,ViewComponentViewComponentBuilder 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 小部件和许多常用组件,如 TreeViewMenuToolbarSiteMapContextMenuDropDownMenuTextboxComobBoxListBoxRichTextBox 等等。如果您对 jQuery MVC 扩展感兴趣,可以访问 http://djme.codeplex.com 进行下载。同时,您也可以访问 http://www.dotnetage.com 尝试在线演示。

© . All rights reserved.