為WebForms說幾句話,以及一些ASP.NET開發(fā)上的經(jīng)驗(yàn)(2)
2007-12-22 22:41 Jeffrey Zhao 閱讀(15878) 評論(150) 收藏 舉報(bào)沒想到我的文章引起了那么大的反應(yīng),看來最近MVC框架的確是一個(gè)熱門話題。正如上一篇文章開始所說的,我不會對MVC框架有任何“貶低”,任何技術(shù)濫用都有問題,所以任何東西都會有所謂的Best Practice(去MSDN的Patterns & Practice欄目看看就知道了)。我寫這幾篇文章,是想說明,很多WebForms的缺點(diǎn)是被夸大了。WebForms的確有缺點(diǎn),但是我們完全可以避開,并且僅僅利用到WebForms給我們帶來的便利。這其實(shí)也是一種Best Practice。相信以后MVC框架也一定會出現(xiàn)這樣的東西。
在上一篇的評論中,有朋友說我是“邊緣”的ASP.NET程序員,大概是因?yàn)槲矣肳ebForms但是拋棄ViewState、PostBack和GridView這種“重要”的東西吧。我不知道這樣是不是“邊緣”,但是我的確拋棄了不少東西。例如還有ADO.NET中的DataSet/DataTable——但是我們還有DataReader呢!拋棄了微軟提供的一部分東西,我們其實(shí)可以發(fā)現(xiàn),.NET里的基礎(chǔ)組件一個(gè)都不少,用起來也會得心應(yīng)手。
去其糟粕,取其精華。
三、生成丑陋的HTML,難以進(jìn)行樣式控制
在ASP.NET的WebForms剛出現(xiàn)時(shí),各種“演示”看上去真的很美。這個(gè)特點(diǎn)微軟至今還保留著,各微軟技術(shù)大會上的演示真的讓人感到心潮澎湃。在我看來,那些“激素大會”更是一種推廣策略,而并沒有將目光集中在技術(shù)細(xì)節(jié)的本身。所以微軟的東西似乎總是有入門容易提高難的“毛病”。開發(fā)人員被“寵壞”了,上一篇文章中有位朋友說這就是“窮人的孩子早當(dāng)家”,還是有一定道理的。在.NET環(huán)境下我們就像是官宦子弟,不過這并不能成為我們習(xí)慣于“吃喝嫖賭”的理由。我們要合理利用富裕的環(huán)境帶給我們的資源,但是要適當(dāng)?shù)貟仐壱恍┎缓玫臇|西。
好像說了幾句廢話,現(xiàn)在進(jìn)入正題。說到WebForms則不得不提豐富的控件,基于ASP.NET平臺的第三方控件提供商似乎比其他平臺下的總合還要多,這也是產(chǎn)業(yè)阿。在微軟提供的控件里,GridView(或者1.1里的DataGrid)是被“演示”次數(shù)最多的,也似乎是最強(qiáng)大(還是差不多等同于“復(fù)雜”)的。有了GridView之后,很多開發(fā)人員就習(xí)慣于朝頁面上拖個(gè)控件,然后再設(shè)計(jì)器里點(diǎn)點(diǎn)鼠標(biāo),設(shè)設(shè)樣式,最后綁定一個(gè)DataTable上去。嘩,好一個(gè)表格就此誕生。然后我們還可以響應(yīng)其事件,對某一行數(shù)據(jù)進(jìn)行編輯,刪除。太輕松了,這世界一下子變得美好了起來。不過由于GridView過于“強(qiáng)大”,它幾乎成為了ASP.NET初學(xué)人員的必修課,當(dāng)對于Web開發(fā)都不明就里的初學(xué)者都習(xí)慣于GridView、GridView、GridView時(shí),它的濫用也就不可避免了。于是乎,做表格,用GirdView,做列表,也用GirdView(不就是個(gè)只有一列的表格嘛)。
可惜的是,GridView有很多毛病。如果要使用GridView的高級功能(添加、修改、刪除等等),ViewState必不可少,再加上很多開發(fā)人員在綁定數(shù)據(jù)時(shí)沒有經(jīng)過篩選,導(dǎo)致ViewState變得無比龐大(例如顯示一個(gè)文章列表,明明只需要ID、標(biāo)題,創(chuàng)建時(shí)間等基本信息,卻用一個(gè)“令人無比愉悅”的SELECT * FROM Article語句把文章內(nèi)容也選擇了出來并綁定在GridView上,ViewState中也就不得不包含幾千幾萬字的多余數(shù)據(jù))。
不過現(xiàn)在想說的,倒是它生成出的HTML。
GridView在客戶端生成的HTML是個(gè)<table />,可惜這個(gè)<table />里東西太多了,的確顯得很丑陋。不過更關(guān)鍵的在于,這個(gè)表格的樣式實(shí)在難以控制。雖說GridView里從標(biāo)題到單元格到低部都能夠進(jìn)行樣式的定義,但是靈活度還是遠(yuǎn)遠(yuǎn)不夠。再加上GridView生成的HTML死板而不符合Web標(biāo)準(zhǔn)(例如不會生成<th />標(biāo)簽,用ControlAdaptor進(jìn)行改造的做法不算),但是優(yōu)秀的樣式開發(fā)人員大都是堅(jiān)定的Web標(biāo)準(zhǔn)支持或推廣者,ASP.NET對于標(biāo)準(zhǔn)支持不佳,難以控制樣式的說法滿天飛揚(yáng)。
我想為WebForms喊冤。不過首先我會打倒以GridView為首的復(fù)雜控件(包擴(kuò)DataList、FormView等等)并狠狠踩上幾腳。有人說,當(dāng)拋棄了GridView之后,用WebForms還有什么意義?其實(shí)類似的話也不斷在我說要拋棄ViewState和(復(fù)雜)的PostBack時(shí)出現(xiàn)。如果您覺得拋棄了這些東西WebForms就失去意義的話,那么我想說,ViewState、PostBack、GridView遠(yuǎn)不是WebForms的全部。我認(rèn)為,Control模型(或者說組件化模型)才是WebForms的關(guān)鍵。而這個(gè)模型的“基礎(chǔ)”是絕對優(yōu)秀的。下面我會進(jìn)行一些展示,雖然這些展示我覺得是基礎(chǔ)中的基礎(chǔ)。
首先我們先來看一下最常用的用戶控件的表現(xiàn)吧(DemoControl.ascx):
<%@ Control Language="C#" AutoEventWireup="true" %>
<%= "Hello World!" %>
然后將它放在頁面里:
<div>
<uc1:DemoControl ID="DemoControl1" runat="server" />
</div>
在瀏覽器里打開頁面會發(fā)現(xiàn)如下的代碼:
<div>
Hello World!
</div>
多干凈的代碼,我甚至連多余的空格都沒有去除。還有一個(gè)例子就是Master Page,<asp:ContentPlaceHolder />也不會產(chǎn)生任何多余的代碼。這說明了使用用戶控件搭出的WebForms頁面,是不會出現(xiàn)多余的“臟”代碼的。如果您在觀察那些基礎(chǔ)控件,TextBox,CheckBox(不設(shè)Text屬性),Panel等等,亦或是加上runat=server的HTML元素,無一例外(當(dāng)然客戶端ID的確還是比較長,關(guān)于這個(gè)問題我會在以后的文章進(jìn)行討論)。
那么骯臟的Tag是哪里來的呢?當(dāng)然是以GridView為首的復(fù)雜控件。那么如果我們要生成批量數(shù)據(jù),又該怎么辦呢?現(xiàn)在來看看Repeater的表現(xiàn)吧,就以最常見的無序列表為例:
<asp:Repeater runat="server" ID="rptItems">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<img src="<%# Eval("ImagePath")) %>" alt="<%# Eval("Title") %>" />
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
還是在瀏覽器里察看HTML(我這里就不貼出來了),一行多余的代碼也沒有。
Repeater是ASP.NET 2.0中我最喜歡用的控件,它的功能很簡單,把ItemTemplate和AlternatingItemTemplate的內(nèi)容返回生成在頁面上,并且將HeaderTemplate和FooterTemplate的內(nèi)容顯示在頭尾。除此之外——沒了。但是這已經(jīng)足夠了,對于綁定控件來說,還需要什么呢?這里面每一行代碼都由我們自己編寫,想定義樣式也易如反掌,我們對于HTML的控制沒有任何損失。
另外,有些開發(fā)人員總認(rèn)為ASP.NET中的DataTable綁定的方式讓我們無法寫出建模良好的代碼。就算使用ObjectDataSource,在控制上也會有諸多不便。但這個(gè)也是一種誤解,我們完全可以將領(lǐng)域模型中的對象綁定到視圖里的控件上。首先是ASPX的內(nèi)容:
<asp:Repeater runat="server" ID="rptItems">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<img src="<%# this.GetImagePath(Container.DataItem) %>"
alt="<%# Eval("Title") %>" />
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
然后是Code Behind:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.rptArticles.DataSource = this.GetArticles();
this.rptArticles.DataBind();
}
protected string GetImageUrl(object dataItem)
{
return "http://img.jeffz.net/" + (dataItem as Article).ImagePath;
}
}
GetArticles方法的返回值可以是List<Article>或任何一個(gè)實(shí)現(xiàn)了IEnumerable<Article>接口的類型,這個(gè)樣在ItemTemplate中訪問Container.DataItem就會得到這個(gè)Article對象。我們在Code Behind類里定義一個(gè)protected方法,由于aspx最終會被編譯為Code Behind類的子類,因此我們就可以在ASPX頁面中調(diào)用GetImageUrl方法。在方法內(nèi)部我們可以將object類型的DataItem轉(zhuǎn)換成Article類型的對象,然后就可以做任意的處理了。
非常方便,非常靈活,還有什么可挑剔的呢?。
有。比如我們想要使用每行10個(gè)元素,最后一行如果不足就讓右邊空著的方式來展示,Repeater可能就難以實(shí)現(xiàn)了(其實(shí)不能這么說,按標(biāo)準(zhǔn)應(yīng)該還是使用無序列表,用樣式來進(jìn)行控制,Repeater完全夠用)。ASP.NET 1.1和2.0可能我們會使用DataList控件,只要把RepeaterColumns屬性設(shè)為10即可。可惜DataList生成的元素要么是<table />,要么是<span />和<br />,讓人頭疼。但是在ASP.NET 3.5中又多了一個(gè)ListView控件,使用這個(gè)控件進(jìn)行展示可以分組循環(huán),可以指定容器,真可謂無比強(qiáng)大。重要的是ListView和Repeater一樣,所有的HTML都由自己控制,一個(gè)多余的字符都沒有。
有了Repeater和ListView,真可謂打遍天下無敵手,任何頁面的展示方式都不在話下。
我們再走個(gè)極端吧,我們來看下面的呈現(xiàn)方式:
<ul> <% foreach (Article article in this.GetArticles())
{ %>
<li>
<img src="<%= "http://img.jeffz.net/" + article.ImagePath %>"
alt="<%= article.Title %>" />
</li>
<% } %>
</ul>
上面的例子在ASPX頁面中使用了內(nèi)聯(lián)的foreach語句進(jìn)行顯示。這樣生成代碼無論從干凈還是自定義角度來說,都可以的讓任何開發(fā)方式“無地自容”。但關(guān)鍵是,這個(gè)方式……為什么……恩,沒錯(cuò),說的難聽,這是原始的ASP的開發(fā)方式;說的好聽,這是“先進(jìn)”ASP.NET MVC框架中模板的寫法。這是不是很諷刺?但是這個(gè)的確是事實(shí)。Rick Strahl大牛也在他的Blog中寫到:“我還記得在早些時(shí)候,那些ASP.NET的瘋狂追隨者們是多么不屑使用內(nèi)聯(lián)的腳本標(biāo)簽,或者使用顯式的URL而不是PostBack來進(jìn)行開發(fā)的做法,而其中的一些人現(xiàn)在又毫不猶豫地張開雙臂去迎接MVC框架,這難道不諷刺嗎?”而且由于ASP.NET MVC的特性(從Controller傳遞到View的只是一個(gè)ViewData對象),因此真正ASP.NET MVC的“模板”的寫法還要多一次Cast,也就是類似于如下的寫法(當(dāng)然ASP.NET MVC可以使用強(qiáng)類型的ViewData,這樣就免去了這樣強(qiáng)行的Cast):
<ul> <% foreach (Article article in (ViewData["Articles"] as List<Article>))
{ %>
<li>
<img src="<%= "http://img.jeffz.net/" + article.ImagePath %>"
alt="<%= article.Title %>" />
</li>
<% } %>
</ul>
這里就要談到ASP.NET MVC了。其實(shí)光從View上來看,我不知道這算不算是進(jìn)步(當(dāng)然其實(shí)我不介意內(nèi)聯(lián)的寫法,有時(shí)候反而更清晰)。ASP.NET MVC如果光從View(模板)方式來看,有點(diǎn)回到了當(dāng)年的ASP方式(當(dāng)然,還是補(bǔ)充了一些Helper)。ASP.NET MVC的特點(diǎn)在于M-V-C的分離,在于業(yè)務(wù)邏輯的觸發(fā)方式,在于URL Routing的驅(qū)動方式,而不是模板化的寫法。如果要說MVC在View層面比ASP.NET清晰,我是肯定不會同意的,因?yàn)槲彝耆梢栽赪ebForms的ASPX頁面中“寫成”ASP.NET MVC類似的方式。不知道您是否同意,至少我認(rèn)為,使用WebForms內(nèi)Repeater或ListView的寫法,不會輸給ASP.NET MVC的模板。而且事實(shí)上,在ASP.NET MVC中作為View使用的aspx頁面里,我們完全也可以放置Repeater,然后再Page_Load方法里寫代碼,這更證明了,在View方面WebForms和ASP.NET MVC其實(shí)是非常相似的。
剛才說了,ASP.NET MVC的重要特點(diǎn),在于M-V-C之間的分離,非常清晰,而且Model和Controller之間能夠進(jìn)行獨(dú)立的測試。但是WebForms也可以做到這一點(diǎn),我在后面的文章中還會談一下WebForms里如何使用MVC來做到清晰、分離和單元測試等等。而且,我似乎覺得某些WebForms中能夠做到的東西在MVC框架里卻無法或者很難實(shí)現(xiàn)。可能是我對于MVC的了解程度還不夠多的緣故吧,等我先研究了MVC框架之后再說。:)
講到這里,您心中是否有了答案,WebForms究竟能否寫出清晰美觀的HTML?在WebForms控制樣式是否困難?拋棄GridView,我們并沒有什么損失,因?yàn)楝F(xiàn)在的網(wǎng)頁中又有多少會用到GridView的功能呢?
目前老趙在公司使用WebForms開發(fā)時(shí)會與一個(gè)專職的前臺開發(fā)人員配合。他是我認(rèn)識的最好的前臺開發(fā)人員,編程能力強(qiáng),經(jīng)驗(yàn)豐富,對于各種瀏覽器中腳本和樣式的差異和問題可謂了如指掌,只是在這之前他只用過PHP,并沒有接觸過ASP.NET WebForms。但是僅僅向他解釋了幾個(gè)控件生成HTML的方式,或者如何從服務(wù)器端獲得ClientID之后,我們之間的配合就非常輕松順暢了。無論是開發(fā)人員先寫放控件然后由他進(jìn)行修飾,還是他先寫靜態(tài)樣式頁面我們再進(jìn)行替換,都工作的非常順利。我現(xiàn)在已經(jīng)能夠?qū)W⒂跇I(yè)務(wù)的實(shí)現(xiàn),架構(gòu)的設(shè)計(jì),而徹底從頁面樣式的“泥潭”中解放了出來,最多編寫一下簡單的JavaScript代碼。分工的明確,也使得我們的工作效率得到了相當(dāng)?shù)奶岣摺?/p>
所以最后我給大家的建議就是,盡可能地使用“純粹”的服務(wù)器端控件。例如使用Repeater和ListView,其實(shí)寫出優(yōu)秀的頁面非常容易。當(dāng)然,即使這么做,還是會有一些缺點(diǎn)的,例如:
- 例如Repeater的ItemCommand事件已經(jīng)無法使用了,因?yàn)樗鼤玫絍iewState。但是我們開發(fā)的大部分的頁面甚至連PostBack都不需要,這又有什么關(guān)系呢?
- 使用服務(wù)器端控件雖然能讓我們對于HTML有十足的控制,但是我們無法避免在客戶端生成一個(gè)不規(guī)則的ID。這一點(diǎn)理論上可能會影響頁面樣式的開發(fā),但是就我那前臺開發(fā)人員同事的話來說,除了一些在客戶端進(jìn)行布局的DOM元素之外,幾乎不會使用ID來控制樣式。就我們工作的結(jié)果來看,不規(guī)則的ID也并沒有造成任何的影響。
相關(guān)文章:
為WebForms說幾句話,以及一些ASP.NET開發(fā)上的經(jīng)驗(yàn)(1):ViewState、性能
為WebForms說幾句話,以及一些ASP.NET開發(fā)上的經(jīng)驗(yàn)(3):生成復(fù)雜的ID難以使用JavaScript操作
未完待續(xù):
五、MVC
六、單元測試
浙公網(wǎng)安備 33010602011771號