2011年2月17日 星期四

ASP.NET MVC 2正式登場

前言:
可以預期的,面對日漸龐雜的資訊應用需求,以及在全球化競爭下的挑戰,特別是台灣在兩岸三地之間特有的整合需求與技術議題,專案軟體開發中架構的彈性以及後續維護的便利性、和軟體開發的自動化測試機制與TDD(Test-driven development)的導入,都將是後續開發團隊需要面對的重要課題,也是台灣軟體產業提升的一個挑戰。
在這次推出的ASP.NET 4.0中,MVC已然併入並且成為ASP.NET新功能中一個相當重要的部分。在接下來的這篇文章當中,我們會帶領讀者理解ASP.NET MVC Framework,從開發人員需求實務上的觀點,引領您進入ASP.NET MVC的世界,並介紹MVC 2當中重要的新功能

        ASP.NET MVC是最容易讓.NET上的Web Forms開發人員困惑且帶有些許爭議的主題。

        當然,如同您所知悉的,MVC Pattern並非微軟提出的(其實早在1979年,MVC就已經被定義出來了),在開發工具領域,同樣也不是微軟率先支持這個設計模式,傳統的ASP.NET Web Forms技術更可說是跟MVC水火不容。就Web開發工具的歷史來看,JSPPHP、甚至ASP都曾經有支援MVC Pattern的套件和產品問世,從設計的架構上來看,在2002年所誕生的ASP.NET更是一丁點都看不出想要支援MVC的影子。
        然而為何在將近10年後的今天,微軟依舊要推出ASP.NET MVC Framework? 為何最後還是選擇支持這個過去不曾考慮過的設計模式? 即便犧牲了ASP.NET Web Forms中所有的控制項支援也在所不惜?

何謂MVC?

 

        在討論ASP.NET MVC 2之前,我們得要明白為何需要MVC Framework,可能讀者在很多地方曾經聽過MVC這樣的名詞,知道MVC是一種PatternMVC這三個縮寫字母分別代表了Model-View-Controller這三個層面的程式碼。

        或者反過來講,MVC這個Pattern要求我們在開發程式的時候,把我們要透過程式碼達成的一個功能,切割成ModelViewController這三個部份(的程式碼),這使得一個簡單的功能,在撰寫的時候比傳統的ASP.NET Web Forms來的複雜許多。
例如底下是典型的BMI(body mass index)計算範例,我們先看View的部分:
EX
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>BMI Index</h2>
     <% using(Html.BeginForm()) {%>
        <br/>身高:<% =Html.TextBox("TxbHeight")%>
        <br/>體重:<% =Html.TextBox("TxbWeight")%>
         <input type="submit" value="Calculate" />
    <% }%>   
    <% if(ViewData["result"] !=null) Response.Write("BMI:" + ViewData["result"]);%>
</asp:Content>

        View是一個.aspx頁面,您會發現MVC應用程式當中的ViewPage上,沒有任何的asp.net控制項,取而代之的是傳統的HTML Code,或是透過In-Line Command(或稱作Code nugget)方式被包裹在<% ... %>當中執行的程式碼。有點回到過去ASP時代那種時空錯置的感覺,但確實是如此。
        ViewPage上可以有各種程式碼,主要的功能就是呈現(Render HTML)頁面,或是接收從Controller傳遞過來的參數(ViewData)。在上面的這段程式碼當中,您會發現我們接收名稱為resultViewData(Controller傳遞來的資料)並將其顯示在畫面上。
        而輸入方塊InputBox也是透過類似『Html.TextBox(…)』這樣的指令碼產生的,這些動作都在在的宣告一件事情,ViewPage(前端頁面)是完全獨立的,並沒有所謂的Code Behind概念、沒有WebControls、沒有過去你熟悉的種種Web Forms功能與支援,但是卻擁有ASP.NET Web Forms所沒有的程式碼獨立性。

        在這樣的架構底下,當您按下Submit鈕把頁面POST回去之後,也並非由網址列所指向的.aspx頁面來處理,取而代之的是底下這樣的Controller,所謂的Controller則是一個繼承於BmiController類別的物件:
EX
public class BmiController : Controller {
    // GET: /Bmi/
    public ActionResult Index() {
        return View();
    }
    // POST: /Bmi/Index
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Index(string TxbHeight, string TxbWeight)  {
        float BMI;
        BMI MyBMI = new MvcApplication1.BMI();
        //使用BMI類別進行運算
        MyBMI.Height = float.Parse(TxbHeight);
        MyBMI.Weight = float.Parse(TxbWeight);
        BMI = MyBMI.GetBmi();
        //將執行結果傳遞給View
        ViewData["result"] = BMI;
        //使用預設的VIew
        return View();
    }
}
       
        我們透過這個Controller類別接收由ViewPage傳遞過來的參數,或是將運算後的結果回傳給ViewPage。而Model最典型的情境,則可用於當運算的結果需要與後端資料庫互動時,所建構出來的Data Model總而言之,如此一來,就形成了底下這樣的架構:
        整個程式的進入點從.aspx頁面轉變成Controller類別,由Controller第一線面對用戶端的需求,決定如何進行邏輯運算,選擇如何呈現頁面,或是調用哪些Model進行資料處理...。然而不管怎麼運作,M-V-C三塊依舊是清楚界限分明且相當獨立,後過去ASP.NET程式碼中.aspx頁面與程式運作邏輯密不可分的狀況可說是迥然不同。(相關的技術概念可參考筆者在Run!PC網站中關於ASP.NET MVC的線上教學影片)

        也因為這樣,當您回頭看整個專案的架構,會發現相當的不同:
        您會發現,我們把程式要完成的一個功能(例如圖中的BMI運算),在撰寫時切割成了不同的幾個部分,分別是呈現在用戶端的/Views/Bmi/Index.aspx頁面,以及負責第一線面對用戶需求的Controllers/BmiConroller.cs,和實際負責運算的BMI.cs類別。

        之所以要盡可能的把程式碼切割成不同的部分,主要的目的是希望能夠降低程式碼彼此之間的相依性,讓每一個獨立的模塊都可以各自獨立開發、各自進行Unit Test、並且提高日後的重用性。而MVC這個pattern的主旨就在將程式碼以Model-View-Controller這樣的概念切割為三塊,同時讓這三塊程式碼之間的相依性降到最低。

        但反觀傳統ASP.NET Web Forms開發人員的習慣,或許是因為透過IDE開發Web應用程式實在是太簡單了,很多開發人員會『一不小心』地就把本來應該要分開成多個類別的程式碼,大辣辣的寫在一個Click事件當中,我們常常會發現開發人員寫出這樣的程式碼:在一個ButtonClick事件當中,進行了資料庫連線、同時透過SQL敘述抓取資料、抓到資料之後還直接撰寫程式碼處理抓取完的資料,接著又將資料BindUI的控制項上,同一段程式碼還可能同時處理前端畫面上UI的調整、還動態的產生了一些JavaScript…這些程式碼都在同一個事件處理函式當中發生!

        儘管這樣寫似乎很直覺很快,卻讓整個應用程式的架構蕩然無存,本來我們希望切割開來的Model-View-Controller三塊程式碼,被開發人員寫在一起,緊密得毫無切割的可能,造成程式碼之間的相依性過高,相對的,未來的重用性就會大幅的降低,也讓單元測試的難度大為提高。而且,這樣的程式碼撰寫方式,同一個開發人員要擅長各種不同領域的技術,一個ASP.NET開發人員,要同時熟悉HTMLCSSJavaScriptSQLVBC#...才能夠完成一個像樣的程式,每一個開發人員針對不同的技術都要有專家級的水準,這在大型專案開發時簡直是不可思議,讓分工相對變的困難。

        從這樣的角度您會發現,越是在開發大型的Web應用程式,越需要MVC這樣的架構,以期達成開發上的切割的便利性、增強軟體的重用性、降低程式碼之間的相依性、同時也能夠享有自動化測試的優點。

        但是不可諱言,這樣的開發方式進入障礙相對的就提高了不少,過去ASP.NET應用程式的開發相當直覺,習慣於Web Forms的開發人員一時之間要進入MVC的世界時所面對的挑戰和開發方式的改變,確實是令人感到有些難以招架,也因此,從ASP.NET MVCPreview版本,一路上增加了不少的功能幫助開發人員能夠更快速輕鬆的進行MVC應用程式的開發,同時也能夠享有MVC開發架構的好處。

ASP.NET MVC 2的重要擴充


        而演變至今,ASP.NET MVC 2包含了哪些新功能呢? 開發人員可以在這個版本的MVC Framework當中,獲得更多有助於提升產能以及開發便利性的新功能。

        諸如Strongly Typed Helpers,配合新版的Visual Studio 2010開發工具,可以幫助開發人員在撰寫ViewPage的時候,使用到Model中的特定資料表欄位時,可以一樣享有如同在使用Entity Framework時相似的Intellisense支援,相當的方便好用:
        你看到上面這段ViewPage中的程式碼,在建立LabelTextBox、以及Validation時,使用到了Model(Customer資料表)中的CustomerID欄位,呈現出來的效果如下圖左。

更有趣的是,當我們在Data Model中針對資料欄位加上的DisplayName這個修飾字之後:

EX
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CustomerID", DbType="NChar(5) NOT NULL", CanBeNull=false, IsPrimaryKey=true)]
[DisplayName("客戶名稱")]
public string CustomerID {
           Get   {
                     return this._CustomerID;
           }
           Set    {
                     if ((this._CustomerID != value)) {
                                this.OnCustomerIDChanging(value);
                                this.SendPropertyChanging();
                                this._CustomerID = value;
                                this.SendPropertyChanged("CustomerID");
                                this.OnCustomerIDChanged();
                     }
           }
}

你會發現呈現出的欄位立即變成支援我們填入的欄位名稱:
        也就是說,ViewPage上的Html Helper,不僅開始支援Intellisense,也支援Model中的Metadata,這些功能將大幅提升MVC應用程式開發速度。不僅僅是如此,在2.0版的MVC當中,Html Helper增強了不少,除了前面看到的Label之外,包含Validation也在支持的行列中。甚至,您可以直接在ViewPage寫上這一行:

EX
<%: Html.EditorForModel() %>


        即可幫你直接配合Model動態產生該頁面的編輯欄位,相關的資料也會截取ModelMetaData,而自動呈現出你需要維護的欄位,如同DynamicData中使用到的DataAnnotation技術一般,方便到了極點,卻又維持著彼此之間的獨立性:
        這一個概念相當重要,這讓MVC應用程式的開發有著接近像我們過去在撰寫Web Forms中使用DetailsView的效果,卻不至於把ViewModel綁在一起,造成本此之間的相依性,也就是說,這個ViewPage並非僅僅針對某一個固定的Table Schema,而是可以用於(配合)後端各種不同的Model

        別忘了這部分的支援還包含了RangeAttribute, RequiredAttribute, StringLengthAttribute, RegexAttribute…等修飾字,這讓MVC ViewPage的設計輕鬆了不少,同時也汲取了DataAnnotation的優點,讓Model來決定前端的呈現方式,如此一來,你可以建立出與後端Data Model無關(相依性極低)的前端View頁面,也就是說,你有可能可以寫一套ViewPage頁面,即可同時處理後端多種不同的Data Mode,讓程式的開發架構更加的有彈性。

安全性提升

 

        另外,您可能已經注意到,在上面我們撰寫的ViewPage當中用到了<%: … %>這樣的與法,與過去我們常用的<%= … %>有所不同。這是因為,在我們透過<%= … %>撰寫程式碼時,需要特別注意輸出的資料是否為JavaScript,例如當開發人員透過<%=ViewData["UserInputTextBox"] %>這樣的一段程式碼輸出資料時倘若UserInputTextBox是來自使用者輸入的欄位,而開發人員卻又忘記針對資料進行Html Encoded,很可能會遭遇到駭客透過Cross Site Scripting方式進行攻擊,在你的輸入欄位中放入JavaScript,使頁面在用戶不知覺的情況下導入其他網站,造成資料外洩或其他更大的損失。

        因此在ASP.NET 4加入的<%: … %>指令碼,可自動將輸出字串進行HtmlEncode動作,如此一來可省去開發人員撰寫程式碼上的時間,亦可確保不致於因為一時的疏忽導致安全上的疑慮。

AsyncController

 

        在新版的MVC 2當中,也加入可以利用非同步方式來呼叫並處理可能長時間執行運算的AsyncController,這對於執行效能的提升將有所幫助。底下這段程式碼展示的是透過AsyncController以非同步方式來抓取資料的方法:
EX
public class PortalController : AsyncController {
    public void NewsAsync(string city) {
        AsyncManager.OutstandingOperations.Increment();
        //呼叫可能執行長時間的服務
        NewsService newsService = new NewsService();
        //設定該呼叫完成要進行的動作
        newsService.GetHeadlinesCompleted += (sender, e) =>
        {
            AsyncManager.Parameters["headlines"] = e.Value;
            AsyncManager.OutstandingOperations.Decrement();
        };
        //已非同步方式執行呼叫
        newsService.GetHeadlinesAsync(city);
    }

    public ActionResult NewsCompleted(string[] headlines)
    {
        //利用View呈現結果
        return View("News", new ViewStringModel
        {
            NewsHeadlines = headlines    //取得非同步呼叫的結果
        });
    }
}

        透過這樣的機制我們可以讓需要長時間執行的服務(例如資料截取、大量運算、網路存取)以非同步的方式來執行,不至於造成系統的過度負擔。

Areas機制的加入

 

        新增加的Areas功能,則可幫助開發人員妥善的組織大型的專案,現在您可以在MVC專案當中增加一個Area
        您會發現整個Area就包含了自己的ControllersViews、與Models,您可以在AreaAreaRegistration.cs檔案中指定Url Route的註冊方式:
EX
public override void RegisterArea(AreaRegistrationContext context) {
    context.MapRoute(
        "NewArea_default",
        "NewArea/{controller}/{action}/{id}",
        new { action = "Index", id = UrlParameter.Optional }
    );
}

        這邊決定了該Area如何回應用戶端的呼叫,在過去沒有AreaMVC 1.0時代,如果專案相當龐大時,View資料夾下的ViewPage頁面可能相當多而複雜,相對的Controllers也會較為紊亂,如今在MVC2當中,您則可以針對需要的功能分類,建立不同的區塊(例如:AdminUserPortal…etc)在再這些區塊中以MVC架構撰寫程式碼,對於大型專案的切割和管理有相當大的幫助。

結語

我知道對於許多ASP.NET開發人員來說,MVC似乎只是一個遙遠的標竿,我們知道它擁有種種的優點,能夠確保程式碼之間的相依性極低、讓重用性大幅提升、同時也是Web應用程式能夠支援TDD(Test-driven development)的基礎。但ASP.NET MVC的高進入門檻,卻讓許多開發人員裹足不前。但就如同我們這幾年常常在台灣聽到的『產業升級』一般,說來容易但執行起來卻不輕鬆,然而這很可能是台灣軟體開發人員開始需要體驗且必經的過程,也是您與您的企業確保未來競爭力的關鍵。

當然,並非每一種專案都適合MVC架構,透別是企業內MIS的臨時性或短期用途的開發需求,講求的是快速上線應用,可能並非MVC架構所適合的。但不可諱言,台灣依舊有相當多大型的Web應用程式專案,是MVC架構能夠妥善發揮其長處的。面對這樣的需求,您依舊可以選擇利用以前熟悉的ASP.NET WebForms來開發,但不妨開始考慮採用ASP.NET MVC作為您在大型Web專案開發上的選擇。特別是Visual Studio 2010配合MVC 2Intellisense以及ViewPage的開發上有著不少改善,在Helper上也有著相當多的增強,可以預期,在專案團隊走過初期的進入障礙門檻之後,透過MVC技術架構下開發的大型專案,後續的果實相較於Web Forms是非常值得令人期待的。

最後,還是一樣,礙於篇幅的關係,我們無法很詳細的介紹所有的ASP.NET MVC 2當中所有的新功能,但讀者可以至筆者部落格http://Blog.StudyHost.Com,或本網誌,除了在本文當中介紹的這些重點之外,後續我們將會有更多更詳細的介紹以及範例程式碼與您分享。
 

沒有留言:

張貼留言