ASP.NET MVC控件項目開發(fā)的簡單分析
【51CTO編者按】對于ASP.NET MVC框架大家一定不會陌生,但是對于很多人來說,弄好一個ASP.NET MVC控件項目實在是費(fèi)勁的事情。這里由作者來介紹他的一個ASP.NET MVC控件項目經(jīng)歷。51CTO編輯推薦《ASP.NET MVC框架視頻教程》
在寫本文之前,本人一直抱著‘不宜’在ASP.NET MVC框架下搞什么控件開發(fā)的想法,因為一提到控件就會讓人想起‘事件’,‘VIEWSTATE’等一些問題,而ASP.NET MVC下是Controller, Action, Viewpage, Filter等特性的‘天下’。所以總感覺‘驢唇對不上馬嘴’。
但直到前陣子在郵箱中收到了關(guān)于telerik關(guān)于MVC框架擴(kuò)展的一些信息之后,才發(fā)現(xiàn)這家商業(yè)控件公司也開始打MVC的主意了。而這個項目(開源)就是該公司在理解了asp.net mvc的基礎(chǔ)上所做的一些嘗試,當(dāng)然其所實現(xiàn)的所謂控件與之前我們在項目中所開發(fā)或使用的web服務(wù)器控件有很大的不同,可以說是拋棄了以往的設(shè)計方式。盡管目前它的這種做法我心里還打著問號,但必定是一種嘗試(不管你贊同還是不贊同)。下面就做一個簡單的分析,希望能給研究MVC架構(gòu)的朋友提供一些的思考。
首先要聲明的是該開源項目中所使用的js就是jquery,而那些顯示效果也基本上就是基于jQuery中的那件插件為原型,并進(jìn)行相應(yīng)的屬性封裝,以便于在viewpage中用C#等語言進(jìn)行聲明綁定。下面就其中一些控件的顯示截圖:
在該開源項目中,所有控件均基于jQueryViewComponentBase (abstract 類型),但其自身屬性并不多,而所有的控件基類屬性都被jQueryViewComponentBase 的父類ViewComponentBase所定義,下面以控件中的“Accordion(屬性頁控件)”為例進(jìn)行說明,見下圖:
上圖中左側(cè)的就是ViewComponentBase類,其定義了多數(shù)控件屬性,比如js腳本名稱和路徑以及相關(guān)樣式以及最終的html元素輸出方法,因為其類也是抽象類,所以其中大部分方法均為定義,而未進(jìn)行具體實現(xiàn)。我們只要關(guān)注一下其構(gòu)造方法就可以了:
- /// <summary>
- /// View component base class.
- /// </summary>
- public abstract class ViewComponentBase : IStyleableComponent, IScriptableComponent
- {
- private string name;
- private string styleSheetFilesLocation;
- private string scriptFilesLocation;
- /// <summary>
- /// 初始化相關(guān)Initializes a new instance of the <see cref="ViewComponentBase"/> class.
- /// </summary>
- /// <param name="viewContext">當(dāng)前視圖的上下文,將會在子類中使用</param>
- /// <param name="clientSideObjectWriterFactory">傳入當(dāng)前所使用的Writer工廠實例.通過子類注入,子類最終延伸到相對應(yīng)的控件實例</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();
- }
通過上述的構(gòu)造方法,就可以將控件的一些通用默認(rèn)屬性值進(jìn)行初始化了。
下面以“Accordion”的源碼來分析一下,這里還是從構(gòu)造方法入手:
- 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;
- }
注:上面的構(gòu)程方法后面加入了base(viewContext, clientSideObjectWriterFactory),以實現(xiàn)向基類構(gòu)造方法傳參,也就是實現(xiàn)了上面所說的將當(dāng)前控件所使用的viewContext,clientSideObjectWriterFactory傳遞到基類ViewComponentBase 中去。(注:最終的clientSideObjectWriterFactory為ClientSideObjectWriterFactory實例類型)。
當(dāng)然,因為該控件的中相應(yīng)屬性比較簡單,只是一些set,get語法,所以就不過多介紹了,相信做過控件開發(fā)的對這些再熟悉不過了。
下面主要介紹一下其write html元素時所使用的方法,如下:
- /// <summary>
- /// 創(chuàng)建并寫入初始化腳本對象和相應(yīng)屬性.
- /// </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 類型實例)中被綁定了相關(guān)的控件屬性,并通過其類的WriteInitializationScript(writer)進(jìn)行腳本的輸出。而基本類的相應(yīng)方法如下:
- /// <summary>
- /// Writes the initialization script.
- /// </summary>
- /// <param name="writer">The writer.</param>
- public virtual void WriteInitializationScript(TextWriter writer)
- {
- }
大家看到該方法為空,但其又是如何運(yùn)行起來的呢,這里先賣個關(guān)子,稍后再說。接著再看一下另一個方法:WriteHtml()
- /// <summary>
- /// 輸出當(dāng)前的 HTML代碼.
- /// </summary>
該方法首先獲取當(dāng)前所選屬性頁標(biāo)簽(GetSelectedItem()方法),然后用foreach方法對屬性頁標(biāo)簽集合進(jìn)行遍歷,并判斷當(dāng)前屬性頁是否就是被選中的屬性頁,并綁定上相應(yīng)的css屬性。其最終也是調(diào)用相應(yīng)的基類方法進(jìn)行輸出。當(dāng)然這里基類方法也是為空,呵呵。
準(zhǔn)備好了這個控件類之后,Telerik還為Accordion控件‘準(zhǔn)備’了一些輔助組件,比如屬性頁組件(AccordionItem),以及相關(guān)的組件構(gòu)造器(AccordionItemBuilder,AccordionBuilder),這樣我們就可以通過這些構(gòu)造器很方便的創(chuàng)建相應(yīng)的控件和組件了,下面就以AccordionItemBuilder為例,解釋一下其構(gòu)造器結(jié)構(gòu):
- public class AccordionBuilder : ViewComponentBuilderBase<Accordion, AccordionBuilder>, IHideObjectMembers
- {
- /// <summary>
對于上面的OnChange方法,可以使用下面的方法將相應(yīng)的js腳本傳入并執(zhí)行
- .OnChange(() =>
- {%>
- function(event, ui)
- {
- $('#trace').append('Change fired: ' + new Date() + '<br/>');
- }
- <%}
- )
這樣,當(dāng)屬性頁發(fā)生變化時,就會在頁面的指定區(qū)域?qū)⒆兓瘯r間顯示出來了,如下圖:
Telerik在jQueryViewComponentFactory中對項目中每一個控件提供了一個方法用以初始化相應(yīng)的構(gòu)造器,以便于創(chuàng)建相應(yīng)的控件,比如Accordion,形如:
- /// <summary>
- /// Creates a accordion for ASP.NET MVC view.
- /// </summary>
而對于其在VIEW中的使用,則通過擴(kuò)展方法來加以聲明:
- 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);
- }
- }
這樣在頁面視圖中,我們這可以使用下面的寫法來構(gòu)造一個Accordion控件了:
- <% Html.jQuery().Accordion()
- .Name("myAccordion")
- .Animate("bounceslide")
- .Items(parent =>
上面只是介紹了前臺和底層代碼如果顯示的問題,但還沒有解釋之前所說的WriteInitializationScript(TextWriter writer)方法以及WriteHtml()
方法如何被調(diào)用的問題,正如之前所看到的,因為Accordion的基類ViewComponentBase中未實現(xiàn)具體的代碼,所以這里我們要將注意力轉(zhuǎn)移到 jQueryViewComponentFactory中,請看如下代碼:
- private TViewComponent Create<TViewComponent>(Func<TViewComponent> factory) where TViewComponent : ViewComponentBase
- {
- TViewComponent component = factory();
上面的方法其實就是之前在該類方法Accordion()中所調(diào)用并執(zhí)行的:
- 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);
- }
- }
當(dāng)組件被成功添加到該list列表中后,系統(tǒng)就會調(diào)用Render()方法將其顯示出來(注:該方法與以前web控件開發(fā)中的顯示方法同名,所以比較好理解),如下:
- /// <summary>
- /// Writes the scripts in the response.
- /// </summary>
注意上面的這一行代碼:
Write(ViewContext.HttpContext.Response.Output);
其所實現(xiàn)的功能如下:
- /// <summary>
- /// 寫出所有腳本資源和腳本 statements.
- /// </summary>
而就是WriteScriptStatements這行代碼開始執(zhí)行之前所說的那個WriteInitializationScript(TextWriter writer)。而WriteHtml()方法的執(zhí)行入口要更加復(fù)雜一些,因為Telerik提供了ViewComponentBuilderBase這個類來進(jìn)行視圖組件的構(gòu)造,而該類中的Render方法就是對相應(yīng)組件的Render方法(組件中已定義)進(jìn)行調(diào)用,如下:
- /// <summary>
- /// Renders the component.
- /// </summary>
- public virtual void Render()
- {
- Component.Render();
- }
而之前的“Accordion”控件是繼承自ViewComponentBase類,所以相應(yīng)組件的Render方法就在該類中進(jìn)行了聲明定義,如下:
- /// <summary>
- /// Renders the component.
- /// </summary>
大家看到了第二行代碼了吧,這就是我們之前看到的那個方法,也就是Accordion組件中WriteHtml()重寫方法的調(diào)用入口。
繞了這么一大圈,才把這個流程理清,是不是有些暈了。的確,剛開始接觸時我也有點(diǎn)暈,但暈呀暈呀就‘暈過去了’,現(xiàn)在再回頭看起來感常見其整體的架構(gòu)思路還是很清晰的??梢哉f有了這瓶酒墊底,再分析該項目中的其它控件就‘如魚得水’了。
***不妨總結(jié)一下:
這是對ASP.NET MVC控件項目開發(fā)做的一次嘗試,但如果之前做過控件特別是web服務(wù)器端控件開發(fā)的朋友,可以看出項目中有非常重的web控件開發(fā)味道,基本連方法名稱都有一定的重疊。
另外就是其自身還是引用了組件對象模型的概念,就拿屬性頁控件來說,就將其分為Accordion和AccordionItem兩種類型,其中可以將Accordion看成是AccordionItem的集合封裝(包括遍歷操作),而這里AccordionItem就成了Accordion的一個組件,而Accordion又是當(dāng)前view中的一個組件。而組件開發(fā)一直是.NET平臺上所倡導(dǎo)的。其優(yōu)勢在于可復(fù)用,維護(hù)方便,簡化復(fù)雜問題等。
原文標(biāo)題:一個Asp.net MVC 控件項目分析---Telerik.Web.Mvc
鏈接:http://www.cnblogs.com/daizhj/archive/2009/09/09/1562966.html
【編輯推薦】