新聞中心
【編者按】對于ASP.NET MVC框架大家一定不會陌生,但是對于很多人來說,弄好一個ASP.NET MVC控件項目實在是費勁的事情。這里由作者來介紹他的一個ASP.NET MVC控件項目經(jīng)歷。編輯推薦《ASP.NET MVC框架視頻教程》

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供岱山網(wǎng)站建設(shè)、岱山做網(wǎng)站、岱山網(wǎng)站設(shè)計、岱山網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、岱山企業(yè)網(wǎng)站模板建站服務(wù),十年岱山做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
在寫本文之前,本人一直抱著‘不宜’在ASP.NET MVC框架下搞什么控件開發(fā)的想法,因為一提到控件就會讓人想起‘事件’,‘VIEWSTATE’等一些問題,而ASP.NET MVC下是Controller, Action, Viewpage, Filter等特性的‘天下’。所以總感覺‘驢唇對不上馬嘴’。
但直到前陣子在郵箱中收到了關(guān)于telerik關(guān)于MVC框架擴展的一些信息之后,才發(fā)現(xiàn)這家商業(yè)控件公司也開始打MVC的主意了。而這個項目(開源)就是該公司在理解了asp.net mvc的基礎(chǔ)上所做的一些嘗試,當然其所實現(xiàn)的所謂控件與之前我們在項目中所開發(fā)或使用的web服務(wù)器控件有很大的不同,可以說是拋棄了以往的設(shè)計方式。盡管目前它的這種做法我心里還打著問號,但必定是一種嘗試(不管你贊同還是不贊同)。下面就做一個簡單的分析,希望能給研究MVC架構(gòu)的朋友提供一些的思考。
首先要聲明的是該開源項目中所使用的js就是jquery,而那些顯示效果也基本上就是基于jQuery中的那件插件為原型,并進行相應(yīng)的屬性封裝,以便于在viewpage中用C#等語言進行聲明綁定。下面就其中一些控件的顯示截圖:
在該開源項目中,所有控件均基于jQueryViewComponentBase (abstract 類型),但其自身屬性并不多,而所有的控件基類屬性都被jQueryViewComponentBase 的父類ViewComponentBase所定義,下面以控件中的“Accordion(屬性頁控件)”為例進行說明,見下圖:
上圖中左側(cè)的就是ViewComponentBase類,其定義了多數(shù)控件屬性,比如js腳本名稱和路徑以及相關(guān)樣式以及最終的html元素輸出方法,因為其類也是抽象類,所以其中大部分方法均為定義,而未進行具體實現(xiàn)。我們只要關(guān)注一下其構(gòu)造方法就可以了:
- ///
- /// View component base class.
- ///
- public abstract class ViewComponentBase : IStyleableComponent, IScriptableComponent
- {
- private string name;
- private string styleSheetFilesLocation;
- private string scriptFilesLocation;
- ///
- /// 初始化相關(guān)Initializes a new instance of the
cref="ViewComponentBase"/> class. - ///
- /// name="viewContext">當前視圖的上下文,將會在子類中使用
- /// name="clientSideObjectWriterFactory">傳入當前所使用的Writer工廠實例.通過子類注入,子類最終延伸到相對應(yīng)的控件實例
- protected ViewComponentBase(ViewContext viewContext, IClientSideObjectWriterFactory clientSideObjectWriterFactory)
- {
- Guard.IsNotNull(viewContext, "viewContext");
- Guard.IsNotNull(clientSideObjectWriterFactory, "clientSideObjectWriterFactory");
- ViewContext = viewContext;
- ClientSideObjectWriterFactory = clientSideObjectWriterFactory;
- StyleSheetFilesPath = WebAssetDefaultSettings.StyleSheetFilesPath;
- StyleSheetFileNames = new List
(); - ScriptFilesPath = WebAssetDefaultSettings.ScriptFilesPath;
- ScriptFileNames = new List
(); - HtmlAttributes = new RouteValueDictionary();
- }
通過上述的構(gòu)造方法,就可以將控件的一些通用默認屬性值進行初始化了。
下面以“Accordion”的源碼來分析一下,這里還是從構(gòu)造方法入手:
- public class Accordion : jQueryViewComponentBase, IAccordionItemContainer
- {
- ……
- ///
- /// Initializes a new instance of the
cref="Accordion"/> class. - ///
- /// name="viewContext">The view context.
- /// name="clientSideObjectWriterFactory">The client side object writer factory.
- public Accordion(ViewContext viewContext, IClientSideObjectWriterFactory clientSideObjectWriterFactory) : base(viewContext, clientSideObjectWriterFactory)
- {
- Items = new List
(); - autoHeight = true;
- }
注:上面的構(gòu)程方法后面加入了base(viewContext, clientSideObjectWriterFactory),以實現(xiàn)向基類構(gòu)造方法傳參,也就是實現(xiàn)了上面所說的將當前控件所使用的viewContext,clientSideObjectWriterFactory傳遞到基類ViewComponentBase 中去。(注:最終的clientSideObjectWriterFactory為ClientSideObjectWriterFactory實例類型)。
當然,因為該控件的中相應(yīng)屬性比較簡單,只是一些set,get語法,所以就不過多介紹了,相信做過控件開發(fā)的對這些再熟悉不過了。
下面主要介紹一下其write html元素時所使用的方法,如下:
- ///
- /// 創(chuàng)建并寫入初始化腳本對象和相應(yīng)屬性.
- ///
- /// name="writer">The writer.
- 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)進行腳本的輸出。而基本類的相應(yīng)方法如下:
- ///
- /// Writes the initialization script.
- ///
- /// name="writer">The writer.
- public virtual void WriteInitializationScript(TextWriter writer)
- {
- }
大家看到該方法為空,但其又是如何運行起來的呢,這里先賣個關(guān)子,稍后再說。接著再看一下另一個方法:WriteHtml()
- ///
- /// 輸出當前的 HTML代碼.
- ///
- protected override void WriteHtml()
- {
- AccordionItem selectedItem = GetSelectedItem();
- TextWriter writer = ViewContext.HttpContext.Response.Output;
- if (!string.IsNullOrEmpty(Theme))
- {
- writer.Write("
class=\"{0}\">".FormatWith(Theme));");- }
- HtmlAttributes.Merge("id", Id, false);
- HtmlAttributes.AppendInValue("class", " ", "ui-accordion ui-widget ui-helper-reset");
- writer.Write("
{0}>".FormatWith(HtmlAttributes.ToAttributeString()));");- foreach (AccordionItem item in Items)
- {
- item.HtmlAttributes.AppendInValue("class", " ", "ui-accordion-header ui-helper-reset ui-state-default ");
- item.ContentHtmlAttributes.AppendInValue("class", " ", "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
- if (item == selectedItem)
- {
- item.ContentHtmlAttributes.AppendInValue("class", " ", "ui-accordion-content-active");
- }
- else
- {
- item.HtmlAttributes.AppendInValue("class", " ", "ui-corner-all");
- }
- writer.Write("
{0}> href=\"#\">{1}
".FormatWith(item.HtmlAttributes.ToAttributeString(), item.Text));- item.ContentHtmlAttributes.AppendInValue("style", ";", (item == selectedItem) ? "display:block" : "display:none");
- writer.Write("
{0}>".FormatWith(item.ContentHtmlAttributes.ToAttributeString()));");- item.Content();
- writer.Write("
- }
- writer.Write("
- if (!string.IsNullOrEmpty(Theme))
- {
- writer.Write("
- }
- base.WriteHtml();
- }
該方法首先獲取當前所選屬性頁標簽(GetSelectedItem()方法),然后用foreach方法對屬性頁標簽集合進行遍歷,并判斷當前屬性頁是否就是被選中的屬性頁,并綁定上相應(yīng)的css屬性。其最終也是調(diào)用相應(yīng)的基類方法進行輸出。當然這里基類方法也是為空,呵呵。
準備好了這個控件類之后,Telerik還為Accordion控件‘準備’了一些輔助組件,比如屬性頁組件(AccordionItem),以及相關(guān)的組件構(gòu)造器(AccordionItemBuilder,AccordionBuilder),這樣我們就可以通過這些構(gòu)造器很方便的創(chuàng)建相應(yīng)的控件和組件了,下面就以AccordionItemBuilder為例,解釋一下其構(gòu)造器結(jié)構(gòu):
- public class AccordionBuilder : ViewComponentBuilderBase
, AccordionBuilder>, IHideObjectMembers
- {
- ///
- /// 初始化方法Initializes a new instance of the
cref="AccordionBuilder"/> class.
- ///
- /// name="component">The component.
- public AccordionBuilder(Accordion component) : base(component)
- {}
- ///
- /// 指定一個屬性頁選項
- ///
- /// name="addAction">要添加的action.
- ///
- public virtual AccordionBuilder Items(Action
addAction)
- {
- Guard.IsNotNull(addAction, "addAction");
- AccordionItemFactory factory = new AccordionItemFactory(Component);
- addAction(factory);
- return this;
- }
- ///
- /// 屬性頁動態(tài)效果顯示名稱(鼠標在屬性頁移入移出時)
- ///
- /// name="effectName">Name of the effect.
- ///
- public virtual AccordionBuilder Animate(string effectName)
- {
- Component.AnimationName = effectName;
- return this;
- }
- ///
- /// 是否高度自適用.
- ///
- /// name="value">if set to
true value.
- ///
- public virtual AccordionBuilder AutoHeight(bool value)
- {
- Component.AutoHeight = value;
- return this;
- }
-
- ///
- /// 指定要觸發(fā)的屬性頁事件名稱.
- ///
- /// name="eventName">Name of the event.
- ///
- public virtual AccordionBuilder OpenOn(string eventName)
- {
- Component.OpenOn = eventName;
- return this;
- }
- ///
- /// 所使用的Icons名稱.
- ///
- /// name="name">The name.
- ///
- public virtual AccordionBuilder Icon(string name)
- {
- Component.Icon = name;
- return this;
- }
- ///
- /// 被選中的屬性頁所使用的Icons 名稱
- ///
- /// name="name">The name.
- ///
- public virtual AccordionBuilder SelectedIcon(string name)
- {
- Component.SelectedIcon = name;
- return this;
- }
- ///
- /// 當屬性頁發(fā)生變化時要傳遞的action 腳本.
- ///
- /// name="javaScript">The java script.
- ///
- public virtual AccordionBuilder OnChange(Action javaScript)
- {
- Component.OnChange = javaScript;
- return this;
- }
- ///
- /// Specify the name of the theme applies to the accordion.
- ///
- /// name="name">The name.
- ///
- public virtual AccordionBuilder Theme(string name)
- {
- Component.Theme = name;
- return this;
- }
- }
對于上面的OnChange方法,可以使用下面的方法將相應(yīng)的js腳本傳入并執(zhí)行
- .OnChange(() =>
- {%>
- function(event, ui)
- {
- $('#trace').append('Change fired: ' + new Date() + '
');
- }
- <%}
- )
這樣,當屬性頁發(fā)生變化時,就會在頁面的指定區(qū)域?qū)⒆兓瘯r間顯示出來了,如下圖:
Telerik在jQueryViewComponentFactory中對項目中每一個控件提供了一個方法用以初始化相應(yīng)的構(gòu)造器,以便于創(chuàng)建相應(yīng)的控件,比如Accordion,形如:
- ///
- /// Creates a accordion for ASP.NET MVC view.
- ///
- ///
- [DebuggerStepThrough]
- public virtual AccordionBuilder Accordion()
- {
- return new AccordionBuilder(Create(() => new Accordion(ViewContext, clientSideObjectWriterFactory)));
- }
而對于其在VIEW中的使用,則通過擴展方法來加以聲明:
- public static class HtmlHelperExtension
- {
- private static readonly IClientSideObjectWriterFactory factory = new ClientSideObjectWriterFactory();
- ///
- /// Gets the jQuery view components instance.
- ///
- /// name="helper">The html helper.
- ///
jQueryViewComponentFactory
- [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
(Func factory) where TViewComponent : ViewComponentBase
- {
- TViewComponent component = factory();
- if (component is jQueryViewComponentBase)
- {
- component.AssetKey = DefaultAssetKey;
- }
- htmlHelper.Telerik().StyleSheetRegistrar().ToRegistrar().Register(component);
- htmlHelper.Telerik().ScriptRegistrar().ToRegistrar().Register(component);
- return component;
- }
上面的方法其實就是之前在該類方法Accordion()中所調(diào)用并執(zhí)行的:
- return new AccordionBuilder(Create(() => new Accordion(ViewContext, clientSideObjectWriterFactory)));
通過該方法,就可以將該控件及其相關(guān)組件信息注冊到相應(yīng)的視圖中。因為我們比較關(guān)注WriteHtml()方法,所以這里就直接分析一下這一行代碼: ScriptRegistrar().ToRegistrar().Register(component); ScriptRegistrar類中的Register方法承擔著將當前要創(chuàng)建的組件添加到當前的腳本組件列表中的任務(wù)(scriptableComponents為list列表)
-
- ///
- /// Registers the scriptable component.
- ///
- /// name="component">The component.
- public virtual void Register(IScriptableComponent component)
- {
- Guard.IsNotNull(component, "component");
- if (!scriptableComponents.Contains(component))
- {
- scriptableComponents.Add(component);
- }
- }
當組件被成功添加到該list列表中后,系統(tǒng)就會調(diào)用Render()方法將其顯示出來(注:該方法與以前web控件開發(fā)中的顯示方法同名,所以比較好理解),如下:
- ///
- /// Writes the scripts in the response.
- ///
- public void Render()
- {
- if (hasRendered)
- {
- throw new InvalidOperationException(Resources.TextResource.YouCannotCallRenderMoreThanOnce);
- }
- if (ViewContext.HttpContext.Request.Browser.EcmaScriptVersion.Major >= 1)
- {
- Write(ViewContext.HttpContext.Response.Output);
- }
- hasRendered = true;
- }
注意上面的這一行代碼:
Write(ViewContext.HttpContext.Response.Output);
其所實現(xiàn)的功能如下:
- ///
- /// 寫出所有腳本資源和腳本 statements.
- ///
- /// name="writer">The writer.
- protected virtual void Write(TextWriter writer)
- {
- WriteScriptSources(writer);
- WriteScriptStatements(writer);
- }
而就是WriteScriptStatements這行代碼開始執(zhí)行之前所說的那個WriteInitializationScript(TextWriter writer)。而WriteHtml()方法的執(zhí)行入口要更加復(fù)雜一些,因為Telerik提供了ViewComponentBuilderBase這個類來進行視圖組件的構(gòu)造,而該類中的Render方法就是對相應(yīng)組件的Render方法(組件中已定義)進行調(diào)用,如下:
- ///
- /// Renders the component.
- ///
- public virtual void Render()
- {
- Component.Render();
- }
而之前的“Accordion”控件是繼承自ViewComponentBase類,所以相應(yīng)組件的Render方法就在該類中進行了聲明定義,如下:
- ///
- /// Renders the component.
- ///
- public void Render()
- {
- EnsureRequired();
- WriteHtml();
- }
大家看到了第二行代碼了吧,這就是我們之前看到的那個方法,也就是Accordion組件中WriteHtml()重寫方法的調(diào)用入口。
繞了這么一大圈,才把這個流程理清,是不是有些暈了。的確,剛開始接觸時我也有點暈,但暈呀暈呀就‘暈過去了’,現(xiàn)在再回頭看起來感常見其整體的架構(gòu)思路還是很清晰的??梢哉f有了這瓶酒墊底,再分析該項目中的其它控件就‘如魚得水’了。
***不妨總結(jié)一下:
這是對ASP.NET MVC控件項目開發(fā)做的一次嘗試,但如果之前做過控件特別是web服務(wù)器端控件開發(fā)的朋友,可以看出項目中有非常重的web控件開發(fā)味道,基本連方法名稱都有一定的重疊。
另外就是其自身還是引用了組件對象模型的概念,就拿屬性頁控件來說,就將其分為Accordion和AccordionItem兩種類型,其中可以將Accordion看成是AccordionItem的集合封裝(包括遍歷操作),而這里AccordionItem就成了Accordion的一個組件,而Accordion又是當前view中的一個組件。而組件開發(fā)一直是.NET平臺上所倡導(dǎo)的。其優(yōu)勢在于可復(fù)用,維護方便,簡化復(fù)雜問題等。
分享名稱:ASP.NETMVC控件項目開發(fā)的簡單分析
文章網(wǎng)址:http://fisionsoft.com.cn/article/cdcighd.html


咨詢
建站咨詢
