【51CTO编者按】对于ASP.NET MVC框架大家一定不会陌生,但是对于很多人来说,弄好一个ASP.NET MVC控件项目实在是费劲的事情。这里由作者来介绍他的一个ASP.NET MVC控件项目经历。51CTO编辑推荐《ASP.NET MVC框架视频教程》
在写本文之前,本人一直抱着‘不宜’在ASP.NET MVC框架下搞什么控件开发的想法,因为一提到控件就会让人想起‘事件’,‘VIEWSTATE’等一些问题,而ASP.NET MVC下是Controller, Action, Viewpage, Filter等特性的‘天下’。所以总感觉‘驴唇对不上马嘴’。
但直到前阵子在邮箱中收到了关于telerik关于MVC框架扩展的一些信息之后,才发现这家商业控件公司也开始打MVC的主意了。而这个项目(开源)就是该公司在理解了asp.net mvc的基础上所做的一些尝试,当然其所实现的所谓控件与之前我们在项目中所开发或使用的web服务器控件有很大的不同,可以说是抛弃了以往的设计方式。尽管目前它的这种做法我心里还打着问号,但必定是一种尝试(不管你赞同还是不赞同)。下面就做一个简单的分析,希望能给研究MVC架构的朋友提供一些的思考。
首先要声明的是该开源项目中所使用的js就是jquery,而那些显示效果也基本上就是基于jQuery中的那件插件为原型,并进行相应的属性封装,以便于在viewpage中用C#等语言进行声明绑定。下面就其中一些控件的显示截图:
在该开源项目中,所有控件均基于jQueryViewComponentBase (abstract 类型),但其自身属性并不多,而所有的控件基类属性都被jQueryViewComponentBase 的父类ViewComponentBase所定义,下面以控件中的“Accordion(属性页控件)”为例进行说明,见下图:
上图中左侧的就是ViewComponentBase类,其定义了多数控件属性,比如js脚本名称和路径以及相关样式以及最终的html元素输出方法,因为其类也是抽象类,所以其中大部分方法均为定义,而未进行具体实现。我们只要关注一下其构造方法就可以了:
- /// <summary>
- /// View component base class.
- /// </summary>
- public abstract class ViewComponentBase : IStyleableComponent, IScriptableComponent
- {
- private string name;
- private string styleSheetFilesLocation;
- private string scriptFilesLocation;
- /// <summary>
- /// 初始化相关Initializes a new instance of the <see cref="ViewComponentBase"/> class.
- /// </summary>
- /// <param name="viewContext">当前视图的上下文,将会在子类中使用</param>
- /// <param name="clientSideObjectWriterFactory">传入当前所使用的Writer工厂实例.通过子类注入,子类最终延伸到相对应的控件实例</param>
- protected ViewComponentBase(ViewContext viewContext, IClientSideObjectWriterFactory clientSideObjectWriterFactory)
- {
- Guard.IsNotNull(viewContext, "viewContext");
- Guard.IsNotNull(clientSideObjectWriterFactory, "clientSideObjectWriterFactory");
- ViewContext = viewContext;
- ClientSideObjectWriterFactory = clientSideObjectWriterFactory;
- StyleSheetFilesPath = WebAssetDefaultSettings.StyleSheetFilesPath;
- StyleSheetFileNames = new List<string>();
- ScriptFilesPath = WebAssetDefaultSettings.ScriptFilesPath;
- ScriptFileNames = new List<string>();
- HtmlAttributes = new RouteValueDictionary();
- }
通过上述的构造方法,就可以将控件的一些通用默认属性值进行初始化了。
下面以“Accordion”的源码来分析一下,这里还是从构造方法入手:
- public class Accordion : jQueryViewComponentBase, IAccordionItemContainer
- {
- ……
- /// <summary>
- /// Initializes a new instance of the <see cref="Accordion"/> class.
- /// </summary>
- /// <param name="viewContext">The view context.</param>
- /// <param name="clientSideObjectWriterFactory">The client side object writer factory.</param>
- public Accordion(ViewContext viewContext, IClientSideObjectWriterFactory clientSideObjectWriterFactory) : base(viewContext, clientSideObjectWriterFactory)
- {
- Items = new List<AccordionItem>();
- autoHeight = true;
- }
注:上面的构程方法后面加入了base(viewContext, clientSideObjectWriterFactory),以实现向基类构造方法传参,也就是实现了上面所说的将当前控件所使用的viewContext,clientSideObjectWriterFactory传递到基类ViewComponentBase 中去。(注:最终的clientSideObjectWriterFactory为ClientSideObjectWriterFactory实例类型)。
当然,因为该控件的中相应属性比较简单,只是一些set,get语法,所以就不过多介绍了,相信做过控件开发的对这些再熟悉不过了。
下面主要介绍一下其write html元素时所使用的方法,如下:
- /// <summary>
- /// 创建并写入初始化脚本对象和相应属性.
- /// </summary>
- /// <param name="writer">The writer.</param>
- public override void WriteInitializationScript(TextWriter writer)
- {
- int selectedIndex = Items.IndexOf(GetSelectedItem());
- IClientSideObjectWriter objectWriter = ClientSideObjectWriterFactory.Create(Id, "accordion", writer);
- objectWriter.Start()
- .Append("active", selectedIndex, 0)
- .Append("animated", AnimationName)
- .Append("autoHeight", AutoHeight, true)
- .Append("clearStyle", ClearStyle, false)
- .Append("collapsible", CollapsibleContent, false)
- .Append("event", OpenOn)
- .Append("fillSpace", FillSpace, false);
- if (!string.IsNullOrEmpty(Icon) || !string.IsNullOrEmpty(SelectedIcon))
- {
- if (!string.IsNullOrEmpty(Icon) && !string.IsNullOrEmpty(SelectedIcon))
- {
- objectWriter.Append("icons:{'header':'" + Icon + "','headerSelected':'" + SelectedIcon + "'}");
- }
- else if (!string.IsNullOrEmpty(Icon))
- {
- objectWriter.Append("icons:{'header':'" + Icon + "'}");
- }
- else if (!string.IsNullOrEmpty(SelectedIcon))
- {
- objectWriter.Append("icons:{'headerSelected':'" + SelectedIcon + "'}");
- }
- }
- objectWriter.Append("change", OnChange).Complete();
- base.WriteInitializationScript(writer);
- }
可以看出,objectWriter (IClientSideObjectWriter 类型实例)中被绑定了相关的控件属性,并通过其类的WriteInitializationScript(writer)进行脚本的输出。而基本类的相应方法如下:
- /// <summary>
- /// Writes the initialization script.
- /// </summary>
- /// <param name="writer">The writer.</param>
- public virtual void WriteInitializationScript(TextWriter writer)
- {
- }
大家看到该方法为空,但其又是如何运行起来的呢,这里先卖个关子,稍后再说。接着再看一下另一个方法:WriteHtml()
- /// <summary>
- /// 输出当前的 HTML代码.
- /// </summary>
该方法首先获取当前所选属性页标签(GetSelectedItem()方法),然后用foreach方法对属性页标签集合进行遍历,并判断当前属性页是否就是被选中的属性页,并绑定上相应的css属性。其最终也是调用相应的基类方法进行输出。当然这里基类方法也是为空,呵呵。
准备好了这个控件类之后,Telerik还为Accordion控件‘准备’了一些辅助组件,比如属性页组件(AccordionItem),以及相关的组件构造器(AccordionItemBuilder,AccordionBuilder),这样我们就可以通过这些构造器很方便的创建相应的控件和组件了,下面就以AccordionItemBuilder为例,解释一下其构造器结构:
- public class AccordionBuilder : ViewComponentBuilderBase<Accordion, AccordionBuilder>, IHideObjectMembers
- {
- /// <summary>
对于上面的OnChange方法,可以使用下面的方法将相应的js脚本传入并执行
- .OnChange(() =>
- {%>
- function(event, ui)
- {
- $('#trace').append('Change fired: ' + new Date() + '<br/>');
- }
- <%}
- )
这样,当属性页发生变化时,就会在页面的指定区域将变化时间显示出来了,如下图:
Telerik在jQueryViewComponentFactory中对项目中每一个控件提供了一个方法用以初始化相应的构造器,以便于创建相应的控件,比如Accordion,形如:
- /// <summary>
- /// Creates a accordion for ASP.NET MVC view.
- /// </summary>
而对于其在VIEW中的使用,则通过扩展方法来加以声明:
- public static class HtmlHelperExtension
- {
- private static readonly IClientSideObjectWriterFactory factory = new ClientSideObjectWriterFactory();
- /// <summary>
- /// Gets the jQuery view components instance.
- /// </summary>
- /// <param name="helper">The html helper.</param>
- /// <returns>jQueryViewComponentFactory</returns>
- [DebuggerStepThrough]
- public static jQueryViewComponentFactory jQuery(this HtmlHelper helper)
- {
- return new jQueryViewComponentFactory(helper, factory);
- }
- }
这样在页面视图中,我们这可以使用下面的写法来构造一个Accordion控件了:
- <% Html.jQuery().Accordion()
- .Name("myAccordion")
- .Animate("bounceslide")
- .Items(parent =>
上面只是介绍了前台和底层代码如果显示的问题,但还没有解释之前所说的WriteInitializationScript(TextWriter writer)方法以及WriteHtml()
方法如何被调用的问题,正如之前所看到的,因为Accordion的基类ViewComponentBase中未实现具体的代码,所以这里我们要将注意力转移到 jQueryViewComponentFactory中,请看如下代码:
- private TViewComponent Create<TViewComponent>(Func<TViewComponent> factory) where TViewComponent : ViewComponentBase
- {
- TViewComponent component = factory();
上面的方法其实就是之前在该类方法Accordion()中所调用并执行的:
- return new AccordionBuilder(Create(() => new Accordion(ViewContext, clientSideObjectWriterFactory)));
- /// <summary>
- /// Registers the scriptable component.
- /// </summary>
- /// <param name="component">The component.</param>
- public virtual void Register(IScriptableComponent component)
- {
- Guard.IsNotNull(component, "component");
- if (!scriptableComponents.Contains(component))
- {
- scriptableComponents.Add(component);
- }
- }
当组件被成功添加到该list列表中后,系统就会调用Render()方法将其显示出来(注:该方法与以前web控件开发中的显示方法同名,所以比较好理解),如下:
- /// <summary>
- /// Writes the scripts in the response.
- /// </summary>
注意上面的这一行代码:
Write(ViewContext.HttpContext.Response.Output);
其所实现的功能如下:
- /// <summary>
- /// 写出所有脚本资源和脚本 statements.
- /// </summary>
而就是WriteScriptStatements这行代码开始执行之前所说的那个WriteInitializationScript(TextWriter writer)。而WriteHtml()方法的执行入口要更加复杂一些,因为Telerik提供了ViewComponentBuilderBase这个类来进行视图组件的构造,而该类中的Render方法就是对相应组件的Render方法(组件中已定义)进行调用,如下:
- /// <summary>
- /// Renders the component.
- /// </summary>
- public virtual void Render()
- {
- Component.Render();
- }
而之前的“Accordion”控件是继承自ViewComponentBase类,所以相应组件的Render方法就在该类中进行了声明定义,如下:
- /// <summary>
- /// Renders the component.
- /// </summary>
大家看到了第二行代码了吧,这就是我们之前看到的那个方法,也就是Accordion组件中WriteHtml()重写方法的调用入口。
绕了这么一大圈,才把这个流程理清,是不是有些晕了。的确,刚开始接触时我也有点晕,但晕呀晕呀就‘晕过去了’,现在再回头看起来感常见其整体的架构思路还是很清晰的。可以说有了这瓶酒垫底,再分析该项目中的其它控件就‘如鱼得水’了。
***不妨总结一下:
这是对ASP.NET MVC控件项目开发做的一次尝试,但如果之前做过控件特别是web服务器端控件开发的朋友,可以看出项目中有非常重的web控件开发味道,基本连方法名称都有一定的重叠。
另外就是其自身还是引用了组件对象模型的概念,就拿属性页控件来说,就将其分为Accordion和AccordionItem两种类型,其中可以将Accordion看成是AccordionItem的集合封装(包括遍历操作),而这里AccordionItem就成了Accordion的一个组件,而Accordion又是当前view中的一个组件。而组件开发一直是.NET平台上所倡导的。其优势在于可复用,维护方便,简化复杂问题等。
原文标题:一个Asp.net MVC 控件项目分析---Telerik.Web.Mvc
链接:http://www.cnblogs.com/daizhj/archive/2009/09/09/1562966.html
【编辑推荐】