天天看點

寫自己的ASP.NET MVC架構(上)

寫了幾篇細說之後,今天打算換換口味,還是來寫代碼吧。 是以,這次部落格将以實際的代碼來展示在ASP.NET平台上開發自己的架構,我希望也能讓您發現這并不是件難事。

我在前面的部落格【用Asp.net寫自己的服務架構】中, 釋出了一個用ASP.NET寫的服務架構,那個架構我目前仍在使用中。近來,由于時常也會有人問我一些關于ASP.NET MVC的話題, 是以,就想再寫個自己的MVC架構出來,一方面可以留給自己使用,另外也可以談談MVC,尤其可以展示一下在ASP.NET下寫架構的樂趣。

我之是以将寫架構看成是件有樂趣的事,是因為:在寫架構的過程中會接觸許多的技術細節。

比如:

1. 為了支援Session需要了解管線過程以及支援Session工作的原理。

2. 在HttpHandler的映射過程中,HttpHandlerFactory的使用會讓設計很靈活。

3. 反射可以讓我們輕松地根據一個URL去尋找比對的Action以及為Action準備傳入參數。

4. 為了便于測試Action,如何有效的封裝架構的功能(這裡有許多ASP.NET的技術細節)。

5. 如何設計讓架構更靈活更強大。

在開始今天的部落格之前,我想有必要說說我的架構的規模:

如果說ASP.NET WebForm是個複雜架構,ASP.NET MVC是個輕量級架構的話,那麼,我的MVC架構将隻是個微量級的架構。

但這個微量級的架構卻可以提供許多實用的功能(因為我沒有引入一些與MVC無關的東西),而且完全遵守MVC的思想而設計。

由于我的架構規模實在太小,是以,有些地方的确是不夠完善,但我認為在大多數情況下是夠用的。

回到頂部

ASP.NET程式的幾種開發方式

時常見到有人在問:到底選擇WebForm還是MVC ?

其實我認為最好是二個都選吧。然後,你去發現它們的優點與缺點,最後,當你覺得它們都不爽時,還是寫自己的架構吧。

我來說說我這樣選擇的理由:任何東西都有它們的優點,這很正常,是以二個都選就能發現更多的優點, 發現優點的過程其實也是自己進步的過程。當然它們也有缺點,發現那些缺點,你自然會想辦法去避開, 其實這也是個進步的過程。是以,在你吸收優點以及避開缺點的過程中,會感覺它們不再完美(因為自己在進步), 再到後來,你會怎麼選擇,我就不知道了,那就是你自己的事了。 而我選擇了另一條路:寫自己的ASP.NET MVC架構。

在比較各類架構之前,我想有必要先來總結一下:現在能用ASP.NET開發哪些類型的網站? 由于ASP.NET與WCF這類純服務性質的架構不同,我們主要還是用它來開發一些可與使用者互動的界面程式。 是以,今天的分類将重要讨論這些界面(UI)的開發方式。

我認為目前的ASP.NET能支援開發三種類型的網站:

1. 以服務端為中心的網站:所有頁面的生成以及互動的邏輯全部服務端來完成,服務端甚至能生成必要的JS代碼。

2. 門戶類網站:服務端隻負責頁面的第一次呈現,使用者的互動以及表單的送出全部采用AJAX的方式完成。

3. 純AJAX網站:服務端基本上不參與UI的處理,隻負責處理資料,UI在用戶端由JavaScript來生成并控制送出。

【以服務端為中心的網站】,這類網站有個非常明顯的特點,至少在開發上表現地非常明顯: 服務端要做的事情很多,HTML的生成, 簡單的JS互動,用戶端驗證,等等,全由服務端來處理。 在開發這類網站時,由于工作全在服務端,是以如果我們使用ASP.NET開發,自然地,所有的任務都将由aspx, C#這類技術來實作, 采用這種方式開發出來的網站,頁面與使用者的互動性通常不會很友好,比如:送出資料時經常需要整頁重新整理。

【門戶類網站】,這類網站與之前的【以服務端為中心的網站】有個重要的差别:頁面隻是呈現資料, 表單的送出全采用AJAX方式了。這樣做的好處能将顯示邏輯與資料更新邏輯有效的分離,不必糾纏在一起(可認為是二個通道), 在這種開發模式下,由于頁面隻負責資料的顯示,是以,隻要能将業務邏輯盡可能地與UI分離,項目在維護上會容易很多, 采用這種方式開發的網站,頁面與使用者互動的友好性會好很多,而且也不會影響SEO,是以被較多的門戶網站采用。

【純AJAX網站】,在這類網站中,服務端由于不參與UI處理,網站可以隻是些靜态的HTML檔案, 而且在設計頁面時,隻要留下一些占位符就可以了,UI元素全部由JS來生成。 這類網站的用戶端通常會選擇一個JS的UI架構來支援。這類界面相對于前二種來說,會更漂亮,使用者的操作體驗也會更友好。 但由于頁面主要由JS來生成,對SEO的支援較差,是以,特别适合一些背景類的網站項目。

在前面所列出的三種開發方式中,前二種由于界面部分由服務端來實作, 是以選擇一個合适的架構,會對開發工作有着非常重要的影響(尤其是第一種)。 但是,如果選擇第三種方式,那麼選擇 WebForm 還是 MVC 真的都是浮雲了,甚至還可以使用其它的服務架構來支援AJAX的調用。

喜歡微軟的MVC架構的一些人,通常會列舉一些WebForm中較為低級的缺點,進而把ASP.NET MVC架構說的很完美,而且是非它不可。 這裡,我不想談論它們的優點與缺點,因為我前面已經說過了,在我看來,它們都有優點也同時有各自的缺點。 今天,我隻想先暫且忘記它們,來實作自己的架構。

開始吧,看看我的作品。

介紹我的MVC架構

我比較喜歡ASP.NET這個平台,因為它們擴充性實在太好了,在它上面,我可以容易地實作自己所需的東西, 包括開發自己所需要的WEB架構。通過微軟的ASP.NET MVC架構,也讓我認識到MVC思想的優點, 是以,我的WEB架構也将采用MVC思想來開發,是以,我把自己的這個架構稱為【我的MVC架構:MyMVC】。 今天的部落格也将向您展示這個架構,同時我也會與大家一起分享我在開發架構過程中所使用到的一些技術(或者稱為實作方式)。

為了讓大家對我的MVC架構有個感性的認識,我準備了一個示例網站,網站提供二種完全不同的風格, 分别采用【門戶類網站】和【純AJAX網站】的方式來開發。 在示例網站的首頁,程式會讓您選擇喜歡的界面風格來繼續後面的操作,當然,您也可以随時在右上角切換風格。

寫自己的ASP.NET MVC架構(上)

我的MVC架構設計架構

在我的架構中,【頁面請求】與【AJAX請求】是分開來實作的。

因為我前面以對開發方式做過分類,在開發【純AJAX網站】時, 那麼就幾乎沒有頁面請求了(或許有,但可以忽略),此時在服務端全是AJAX服務(我喜歡将AJAX的服務端實作稱為服務)。

我将AJAX請求分開來處理是因為: 我做的網站中,AJAX的使用非常多,數量都會超過頁面請求,而且有時甚至沒有ASPX頁面,全是AJAX調用,是以我更看重AJAX。

二種請求(我稱為通道)大緻是這樣的處理過程:

寫自己的ASP.NET MVC架構(上)

說明:示意圖中并沒有直覺地反映出【頁面請求】與【AJAX請求】在處理過程中的差别, 但這個差别是存在的,差别主要在于從URL到Action的映射過程,後面會有詳細地介紹。

以下示意圖表示了【我的MVC架構】在處理一個請求時的具體過程:

寫自己的ASP.NET MVC架構(上)

今天的部落格内容将主要介紹這個架構如何實作AJAX請求處理,頁面請求的實作過程将在後續的部落格中介紹。

回憶以往AJAX的實作方式

我的MVC架構對AJAX的支援來源于我對代碼不斷重構的過程,為了更好地了解我的MVC架構, 我認為有必要先來回憶一下以往是如何(在服務端)實作AJAX的。

在ASP.NET中有一種比較原始的實作Ajax的方式,那就是建立一個ashx,就像下面的代碼: 

寫自己的ASP.NET MVC架構(上)

當然了,也有人會選擇建立一個空的aspx去代替ashx,而且使用aspx還可以隻輸出一個HTML片段。

在這種原始的方式下,整個處理過程可以大緻分為注釋中所标注的三個階段。 如果使用這種方式去做服務端的AJAX開發,當AJAX的數量到達一定規模後,可以發現:大量的代碼是類似。 我之是以稱為【類似】,是因為它們卻實有差别,差别在于:參數的名字不同,參數的類型不同,參數的個數不同,要調用的方法以及傳回值不同。

說實話,這種機械代碼我也寫過。

不過,當我發現時這個現象時,我就開始想辦法去解決這個問題,因為我非常不喜歡寫這類重複性質的代碼。

在重構過程中,也逐漸形成了我自己的AJAX服務端架構。

後來我把它寫到我的第一篇部落格中了: 【曬曬我的Ajax服務端架構】

在AJAX的發展過程中,微軟曾經推出過ASP.NET AJAX架構,它可以在服務端生成一些JS的代理類,讓用戶端的JS友善地調用服務端的方法。 雖然那個架構設計地很巧妙,并且與WebForm配合地很完美,隻可惜那個架構不夠流行。 後來的WCF通過一些配置也可以讓JS去調用,不過,喜歡的人也不多,可能還是因為配置麻煩的緣故吧。 當後來微軟推出了ASP.NET MVC架構時,一些人開始驚呼:AJAX非ASP.NET MVC架構不可。 因為ASP.NET MVC架構可以很容易讓JS去調用一個C#方法,從此以後,再也不用去【讀參數,調用方法,寫輸出】這些繁瑣的事情了, 而且沒有WCF那麼複雜的配置。 的确,他們沒有解決的問題,ASP.NET MVC架構很好地解決了。

今天的部落格,我将向大家介紹我的AJAX解決方案,它同樣可以很好的解決上面的那些繁瑣的過程。

MyMVC中實作AJAX的方式

在我的架構中,服務端可以很容易地将一個C#方法公開給用戶端的JavaScript來通路,比如下面這個C#方法:

public class AjaxOrder
{
    [Action]
    public void AddOrder(OrderSubmitForm form)
    {
        Order order = form.ConvertToOrderItem();
        BllFactory.GetOrderBLL().AddOrder(order);
    }
      

那麼用戶端就可以通過這個URL位址來調用那個方法:"/AjaxOrder/AddOrder.cspx" ,

URL中的一些名稱與C#類名以及方法的名稱的對應關系,請參考下圖。

至于C#方法所需的參數,你就不用擔心了,架構會替您準備好,你隻要通路就可以了。

說明:這個Action太簡單了,連傳回值也沒有。後面會有傳回值的示例代碼,請繼續閱讀。

前面的示例可以用下面的圖形來表示C#代碼與URL的映射關系:

寫自己的ASP.NET MVC架構(上)

補充說明一下:按照MVC的标準術語,下文将這類用于處理請求的方法将稱為【Action】,Action所在的類型稱為Controller。 不過,在我的MVC架構中,Action又分【PageAction】和【AjaxAction】。 而且,在我的MVC架構中,對Controller限制極少,不會要求您繼承什麼類型或者實作什麼接口,Controller甚至可以是個靜态類。

唯獨隻要求:1. 包含AjaxAction的Controller必須以Ajax開頭, 包含PageAction的Controller必須以Controller結尾(照顧喜歡微軟MVC架構的使用者)。 加這個限制僅僅是為了快速定位Action,并沒有其它原因。 2. 類型與方法的可見性為 public (同樣僅僅隻是為了快速定位) 。

是以,在我的架構中,Controller的意義将隻是一個Action的容器。

如何使用MyMVC架構中的AJAX功能

在我的MVC架構中,JS幾乎可以透明地直接調用C#方法。比如我有這樣一個C#方法:

public class AjaxDemo
{
    [Action]
    public string GetMd5(string input)
    {
        if( input == null )
            input = string.Empty;

        byte[] bb = (new MD5CryptoServiceProvider()).ComputeHash(Encoding.Default.GetBytes(input));
        return BitConverter.ToString(bb).Replace("-", "").ToLower();
    }
}
      

方法很簡單,可以計算一個字元串的MD5值。下面再來看一下如何在JS中調用:

$("#btnGetMd5").click(function(){
    $.ajax({
        // 以下二個URL位址都是有效的。
        //url: "/AjaxDemo/GetMd5.cspx",
        url: "/AjaxDemo.GetMd5.cspx",
        data: {input: $("#txtInput").val()},
        success: function(responseText){
            $("#spanReslt").text(responseText);
        }
    });
});
      

說明一下:這裡我使用JQuery這個JavaScript類庫來完成用戶端的部分。

在JS代碼中,我通過一個URL位址就可以直接通路到前面所定義的C#方法,C#方法所面的參數由$.ajax()的data參數指定。 由于實在過于簡單,我感覺不需要再對這個示例做更多的解釋。

唯獨我要提醒的是:為了安全,JS并不能調用任何一個C#方法(雖然在技術上沒有任何難度)。 是以,如果您允許一個C#方法公開給JS調用,那麼方法必須加[Action]這個Attribute 。

在前面的示例中,方法的傳入參數以及傳回值的類型都比較簡單,事實上,MyMVC也可以支援複雜的資料類型。 例如,以下方法的簽名都是有效的: 

寫自己的ASP.NET MVC架構(上)

有了MyMVC,就幾乎上不需要再去通路QueryString,Form這些對象了。 你需要什麼參數,隻要寫在方法的簽名中就可以了。參數可以是簡單的資料類型,也可以是自定義的資料類型,參數的個數也沒有限制。

不過,有一點我要提醒您:所有的資料來源隻有二個地方:QueryString和Form,架構隻讀取這二個地方,而且直接通路它們的索引器。 由于QueryString,Form這二個類型都是NameValueCollection,而NameValueCollection的索引器在實作上有點獨特,是以請大家注意它們的傳回值。 關于NameValueCollection的細節描述,可以參考我的部落格【細說 Request[]與Request.Params[]】, 今天我就不再重談這個細節話題了。

在讀取參數時,萬一出現key重複了怎麼辦?

架構還提供另一種解決方案,那就是您可以在C#的方法的簽名中,聲明NameValueCollection類型的變量,變量名可以從【Form,QueryString,Headers,ServerVariables】中選擇。 注意:對于後二者,架構本身也是不讀取的,如果需要讀取,隻能使用這個方法來擷取。示例代碼如下: 

寫自己的ASP.NET MVC架構(上)

代碼中,我同時要求架構給出這四個集合,事實上,您可以根據實際情況來決定需要多少個參數。

注意:

1. 參數名稱是大小寫【不敏感】的。

2. 類型一定要求是NameValueCollection 。

3. 架構會優先讀取QueryString,如果沒有則會檢視Form

4. 千萬不要在Action中使用HttpContext.Current.Request.QueryString[]的方式讀取來自用戶端的參數。

關于參數,還有一種特殊的情況:我在部落格【細說 Form (表單)】中曾提到過, 例如,我有這樣二個類型,它們的結構一樣: 

寫自己的ASP.NET MVC架構(上)

如果此時我有這樣一個C#方法,又該如何處理呢? 

寫自己的ASP.NET MVC架構(上)

上面的示例也可以了解成:一模一樣的參數類型,就是要出現多次,再或者,多個不同的自定義類型中,有些成員的名稱是相同的。

此時我的架構在設計時與微軟的MVC架構一樣,要求在HTML中對name做特殊的設定,示例代碼如下: 

寫自己的ASP.NET MVC架構(上)

此時要求:input标簽中的name必須能夠反映C#方法的參數名以及類型中所包含的資料成員名稱。

注意:在MyMVC架構中,自定義的資料類型所包含的資料成員不要求是屬性,字段(Field)也是完全受支援的。

配置MyMVC架構

MyMVC架構在使用前,必須配置。

在前面的示例中,"/AjaxDemo2/TestCustomerType.cspx" 這樣的URL位址,按照ASP.NET的預設設定,它是不能被映射到一個有效的處理器的, 那時,将出現一個404異常。是以,為了使用MyMVC中對AJAX的支援,必須做以下配置:

<httpHandlers>
    <add path="*Ajax*/*.cspx,*Ajax*.*.cspx" verb="*" 
                                type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true"/>
</httpHandlers>
      

如果在IIS7的環境中運作,還需要以下配置: 

寫自己的ASP.NET MVC架構(上)

在示例代碼中,我使用了【cspx】這個擴充名,如果您不喜歡,也可以選擇您所喜歡的擴充名,這個不是問題。

關于配置參數中的【path】屬性,請參考我的上篇部落格【細說 HttpHandler 的映射過程】,這裡也不再重新解釋。 如果沒有看過的,建議還是去看一下,下面将會用到那些知識,因為它非常重要。

MyMVC架構的實作原理 - 映射處理器(入口)

前面談到了MyMVC架構的配置,通過那個配置,相當于在ASP.NET中為MyMVC注冊了一個入口點。

根據上面的配置,符合條件的請求将會被映射給AjaxHandlerFactory。既然是這樣,我們來看一下這個入口點的實作代碼: 

寫自己的ASP.NET MVC架構(上)

代碼中,每個步驟做了什麼事情,注釋中有說明,不需要再重複說明。最後建立的ActionHandler的實作代碼如下: 

寫自己的ASP.NET MVC架構(上)

整個入口點就是這樣的。

有沒人想過:為什麼不直接在web.config中映射到這個ActionHandler呢?

答案在後面,請繼續閱讀。

MyMVC架構的實作原理 - 對Session的支援

前面有一個方法的實作我故意沒有貼出,那麼是ActionHandler.CreateHandler()這個靜态方法。現在是時候來貼它了: 

寫自己的ASP.NET MVC架構(上)

這段代碼又涉及另外二個類型,它們的實作代碼如下:

internal class RequiresSessionActionHandler : ActionHandler, IRequiresSessionState
{
}

internal class ReadOnlySessionActionHandler : 
                        ActionHandler, IRequiresSessionState, IReadOnlySessionState
{
}
      

不要感到奇怪,這二個類型的确沒有任何代碼。

它們除了從ActionHandler繼承而來,還實作了另外二個接口, 那二個接口我在部落格【Session,有沒有必要使用它?】中已有詳細的解釋, 不明白的朋友,可以去閱讀那篇部落格。

再來回答前面那個問題:為什麼不直接在web.config中映射到這個ActionHandler呢?

答:如果這樣配置,那麼對Session的支援将隻有一種模式!

在這個架構中,我采用HttpHandlerFactory就可以輕松地實作對多種Session模式的支援。

說到這裡,我真的感覺上篇部落格【細說 HttpHandler 的映射過程】的研究成果太有意義了, 是它給【MyMVC對Session完美的支援】提供了靈感。

老實說:我是不使用Session的。

但看到以前的部落格中有些人還是堅持使用Session,是以就決定在MyMVC中支援這個功能, 畢竟支援Session不是件難事。

下面再來說說如何支援Session 。 

寫自己的ASP.NET MVC架構(上)

在上面這段代碼中,我加了一個[SessionMode]的Attribute,用它可以指定Action的Session支援模式, SessionMode是個枚舉值,定義如下: 

寫自己的ASP.NET MVC架構(上)

MyMVC架構支援以上三種不同的Session模式,預設是關閉的,如果需要使用,請顯式指定。 [SessionMode]既可以用于Controller類型,也可以用于Action 。

注意:Session的使用将會給Action的單元測試帶來麻煩。

MyMVC架構的實作原理 - 對OutputCache的支援

MyMVC架構對OutputCache也有着很好的支援。下面的代碼示範了如何使用OutputCache:

[OutputCache(Duration=10, VaryByParam="none")]
[Action]
public string TestOutputCache()
{
    return DateTime.Now.ToString();
}
      

如果在浏覽器中通路這個位址:http://localhost:34743/AjaxDemo/TestOutputCache.cspx

會發現結果在10秒鐘内不會有改變(F5重新整理),如果打開Fiddler2,會看到304的響應。

寫自己的ASP.NET MVC架構(上)

[OutputCache]所支援的屬性較多,這裡就不一一列出了,下面再來說說它的實作原理。

我在部落格【細說 ASP.NET控制HTTP緩存】曾分析過ASP.NET Page的緩存頁實作原理, 其中有個小節【緩存頁的服務端程式設計】專門分析了Page對OutputCache的實作過程,在MyMVC中,就是使用的這種方法,具體過程可以參考那篇部落格。 補充一句:微軟的ASP.NET MVC也是這樣做的,它也是借助了Page的強大功能。

MyMVC中的代碼: 

寫自己的ASP.NET MVC架構(上)

在OutputCacheAttribute類的用法中,清楚地指出适用于類型與方法,是以,這個Attribute可以用于Controller和Action 。

說明:OutputCacheAttribute與SessionModeAttribute類似,都可以用于Controller和Action,同時使用時,Action優先比對,代碼如下: 

寫自己的ASP.NET MVC架構(上)

是以,架構隻要選擇一個時機調用SetResponseCache()方法就可以了,至于這個調用時機出現在哪裡,請繼續閱讀。

MyMVC架構的實作原理 - 查找Action的過程

前面有張圖檔反映了從URL位址到Action的映射過程:

寫自己的ASP.NET MVC架構(上)

下面再來談談這個過程的實作。

首先,我們要先在web.config中注冊MyMVC的HttpHandlerFactory,它是整個架構的入口。

在ASP.NET的管線過程中,會調用GetHandler()方法,終于我的代碼有機會運作了!

架構執行的第一行代碼是:

// 根據請求路徑,定位到要執行的Action
ControllerActionPair pair = UrlParser.ParseAjaxUrl(virtualPath);

      

ControllerActionPair是我定義的一個表示Controller以及Action名字的值對類型:

public sealed class ControllerActionPair
{
    public string Controller;
    public string Action;
}
      

靜态方法UrlParser.ParseAjaxUrl()就是專門用來解析URL并傳回ControllerActionPair的: 

寫自己的ASP.NET MVC架構(上)

代碼很簡單,核心其實就是那個正規表達式,從URL中提取Controller,Action的名字全靠它。

至于正規表達式的使用,我想這是個基本功,這裡就略過了。

再來看AjaxHandlerFactory的第二個調用:

// 擷取内部表示的調用資訊
InvokeInfo vkInfo = ReflectionHelper.GetAjaxInvokeInfo(pair);

      

ReflectionHelper類是一個内部使用的工具類,專門用于反射處理,AjaxAction查找過程的相關代碼如下(注意代碼中的注釋): 

寫自己的ASP.NET MVC架構(上)

上面就是AjaxAction查找相關的4段代碼:

1. 在ReflectionHelper的靜态構造函數中,我加載了所有AjaxController。

2. GetAjaxController方法用于根據一個Controller的名字傳回Controller的類型描述。

3. GetAjaxAction方法用于根據Controller的類型以及要調用的Action的名字傳回Action的描述資訊。

4. GetAjaxInvokeInfo方法用于根據從AjaxHandlerFactory得到的ControllerActionPair描述轉成更具體的描述資訊。

代碼中,Action的查找過程采用了延遲的加載模式,儲存Action描述資訊的集合我采用了線程安全的Hashtable

好了,上面那段代碼我想說的就這些,剩下的就隻些反射的使用,這也算是個基本功,而且也不是三言二語能說清楚的。 是以,我打算繼續談其它的内容了。

MyMVC架構的實作原理 - 執行Action的過程

在AjaxHandlerFactory的GetHandler方法中,最後将建立一個ActionHandler,這是一個HttpHandler, 它将在管線的第15個步驟中被調用(引用部落格【用Asp.net寫自己的服務架構】中的順序)。

注意:AjaxHandlerFactory的GetHandler方法是在第10步中調用的,第12步就是在準備Session(非程序内模式), 是以,必須在第12步前決定Session的使用方式。

所有的Action代碼都是在ActionHandler中執行的: 

寫自己的ASP.NET MVC架構(上)

ExecuteAction的實作過程如下: 

寫自己的ASP.NET MVC架構(上)

前面我不是沒有說調用SetResponseCache()的時機嘛,這個時機就是在這裡:執行完Action後。

設定過OutputCache後,就是處理傳回值了。

前面那段代碼中,還有一句重要的調用:

// 準備要傳給調用方法的參數
object[] parameters = GetActionCallParameters(context, info.Action);

      

這個調用的意義在注釋中有解釋,關于這個過程的實作方式還請繼續閱讀。

MyMVC架構的實作原理 - 如何給方法指派

用過反射的人都知道,調用一個方法很簡單,但如何給一個【不知簽名】的方法準備傳入參數呢?

下面就來回答這個問題,請接着看GetActionCallParameters的實作過程: 

寫自己的ASP.NET MVC架構(上)

要了解這段代碼還要從前面的【查找Action的過程】說起,在那個階段,可以擷取一個Action的描述,具體在架構内部表示為ActionDescription類型: 

寫自己的ASP.NET MVC架構(上)

在構造函數的第三行代碼中,我就可以得到這個方法的所有參數情況。

然後,我在就可以在GetActionCallParameters方法中,循環每個參數的定義,為它們指派。

這段代碼也解釋了前面所說的隻支援4種NameValueCollection集合的原因。

注意了,我在擷取每個參數的類型時,是使用了下面的語句:

Type paramterType = p.ParameterType.GetRealType();

      

實際上,ParameterType就已經反映了參數的類型,為什麼不直接使用它呢?

答:因為【可空泛型】的原因。這個類型我們需要特殊的處理。

例如:如果某個參數是這樣聲明的: int? id 

那麼,即使在QueryString中包含id這樣一個參數,我也不能直接轉成 int? 使用這種類型,必須得到它的【實際類型】。

GetRealType()是個擴充方法,它就專門完成這個功能: 

寫自己的ASP.NET MVC架構(上)

如果某個參數的類型是一個自定義的類型,架構會先建立執行個體(調用無參的構造函數),然後給它的Property, Field指派。

注意了:自定義的類型,一定要提供一個無參的構造函數。

為自定義類型的執行個體填充資料成員的代碼如下: 

寫自己的ASP.NET MVC架構(上)

在給自定義的資料類型執行個體加載資料前,需要先知道這個執行個體對象有哪些屬性以及字段,這個過程的代碼如下: 

寫自己的ASP.NET MVC架構(上)

在拿到一個類型的所有屬性以及字段的描述資訊後,就可以通過循環的方式,根據這些資料成員的名字去QueryString,Form讀取所需的資料了。

參數準備好了,前面的調用就應該沒有問題了吧?

MyMVC架構的實作原理 - 處理傳回值

MyMVC架構處理傳回值的時機是在ExecuteAction方法中(前面有那段代碼)。

這裡隻做個簡單的補充說明。

我為Action的結果定義了一個接口:

public interface IActionResult
{
    void Ouput(HttpContext context);
}
      

架構内實作了4種ActionResult: 

寫自己的ASP.NET MVC架構(上)

要輸出傳回值的時候,不僅使用了IActionResult接口,我還使用下面這個調用:

context.Response.Write(result.ToString());

      

不要小看了ToString()的調用。

對于自定義的資料類型來說,可以用它來控制最終輸出給用戶端的是JSON或者是XML, 或者是您自己定義的文本序列化格式(比如:特殊分隔符拼接而成), 是以,它有足夠的能力可以取代JsonResult類型,而且同樣不影響Action的單元測試。

ToString()的強大原因在于它是個虛方法,可以被派生類重寫。

是以,如果您隻打算傳回一個資料實體對象給用戶端,那麼既可以實作IActionResult接口,還可以重寫ToString方法。

MyMVC架構的實作原理 - 如何傳回HTML片段

AJAX調用中,雖然以傳回資料居多,但有時也會要求傳回一段HTML,畢竟拼HTML代碼在服務端會容易些。

MyMVC提供UcResult類型,用來将一個使用者控件的呈現結果做為HTML輸出。 當然了,您也可以建立一個Page,采用Page來輸出HTML,那麼就要用到PageResult類型了。 它們的使用代碼如下: 

寫自己的ASP.NET MVC架構(上)

由于我從來不用Page輸出一段HTML,是以沒有準備在Ajax中使用PageResult的示例。 但是,它們的使用方法是一樣,因為:PageResult和UcResult的構造函數有着一緻的簽名方式。

再來說說建立UcResult對象那行代碼:傳入二個參數,第一個參數表示使用者控件的位置(View),第二個參數表示呈現使用者控件所需的資料(Model)。 至于這個地方為什麼要設計二個參數,請關注我的後續部落格,因為它涉及到MVC的核心思想,今天的部落格不打算談這個話題。

MyMVC架構的實作原理 - 多命名空間的支援

前面的示例代碼都是示範了如何設計一個能供JS調用的Action,事實上,您也看到了,其實就是加了個[Action]的方法而已,沒有其它的特别之處了。 不過,在現實開發中,類型的名字可能會沖突。比如:.NET就引入了命名空間來處理這種沖突的類名。

MyMVC支援同名的Controller的名字嗎?

答案是肯定的:支援。

例如,我有下面二個類型。注意它們的名字是相同的。 

寫自己的ASP.NET MVC架構(上)

這二個類型不僅同名,而且還包含了同名的方法。(事實上,方法的簽名也可以完全一樣。)

那麼,對于這種情況,JS如何去調用它們呢?

為了回答這個問題,我特意準備了一個示例,HTML代碼如下: 

寫自己的ASP.NET MVC架構(上)

用戶端的JS代碼如下: 

寫自己的ASP.NET MVC架構(上)

最終的調用結果如下:

寫自己的ASP.NET MVC架構(上)

注意:下方的調用結果雖然是錯誤的,但表示調用的方法是正确的。

讓我們再來回顧一下UrlParser類中定義的那個正規表達式吧:

internal static readonly string AjaxUrlPattern
    = @"/(?<name>(\w[\./\w]*)?(?=Ajax)\w+)[/\.](?<method>\w+)\.[a-zA-Z]+";

      

它可以解析這些格式的URL:

/*
    可以解析以下格式的URL:(前三個表示包含命名空間的)

    /Fish.AA.AjaxTest/Add.cspx
    /Fish.BB.AjaxTest.Add.cspx
    /Fish/BB/AjaxTest/Add.cspx
    /AjaxDemo/GetMd5.cspx
    /AjaxDemo.GetMd5.cspx
*/
      

值得說明的是:這個正規表達式并沒有限定用什麼樣的擴充名,而且也不限制URL中的查詢字元串參數。

但是,就算它再強大,還需要在web.config中注冊時,要保證比對的URL能被傳入,否則代碼根本沒有機會運作。

重溫httpHandlers的注冊:

<httpHandlers>
    <add path="*Ajax*/*.cspx,*Ajax*.*.cspx" verb="*" 
            type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true"/>
</httpHandlers>
      

感謝微軟的天才設計,讓我可以用通配符的方式寫正規表達式。

關于反射的使用

反射。

我想有些人聽到這個名字,首先想到的會是低性能,并積極地拒絕使用。

在那些人的心目中,反射就是低性能的代名詞。

有趣的是,那些人可能在樂滋滋地用着ASP.NET MVC, WCF, EntryFramewok這類架構。

這裡我要說明的是,我并沒有說那些架構比較差,而是想說:

那些架構其實也在大量地使用反射,隻是微軟沒有直接說出來而已。

我不知道那些不喜歡的反射的人,知道這些架構在大量使用反射時,會有什麼樣的想法。

其實想知道一個架構有沒有在使用反射,有個簡單的識别方法:

1. 它有沒有序列化和反序列化。

2. 有沒有把類名與方法寫在字元串中。

3. 它是不是可以神奇地知道你的任何對象擁有哪些成員?

4. 有沒有使用[Attribute]。您不會以為這個标記是給編譯器看的吧?

WCF簡直是把這些全用上了,而且是在大量使用,ASP.NET MVC,EntryFramewok也沒少用!

在實作MyMVC的過程,我大量地使用了反射。

沒辦法,不用反射,我真的寫不出來什麼東西。

我認為:沒有哪個架構可以不使用反射的。

不使用反射,就意味着:在事先就需要知道将調用哪些類型的哪些方法,這樣哪來的靈活性?

反射還有另一個好處就是簡化代碼,許多類似的代碼,就像前面【回憶以往AJAX的實作方式】中總結的那樣。 那些類似的代碼差别在于:參數的名字不同,參數的類型不同,參數的個數不同,要調用的方法以及傳回值不同。 那些驚呼【非ASP.NET MVC架構不可】的人或許也是厭倦了這些重複勞動,然而,ASP.NET MVC解決這個問題的辦法還是反射。

是以,不必害怕反射,它的确會影響性能。

但是,你可以保證你的其它代碼都是性能很好嗎?

我見過的低性能代碼實在是太多了。

反射是會影響性能,但好消息是,它對性能的影響是可以優化的,是以,不同的寫法,所表現出來的影響也是不一樣的。 不過,反射的優化也是個複雜的話題,我打算以後有機會再談。

結束語

今天的部落格示範了我的MVC架構對于AJAX的支援,也展示了在ASP.NET上開發一個架構的具體過程, 雖然還未全部說完,但核心部分已經實作了,那就是:根據URL動态調用一個方法。 先說AJAX的實作是因為,它是【無界面】的,無界面的東西通常會比較簡單。

說到【無界面】又讓我想到一些人把微軟的ASP.NET MVC用于【無界面】的項目, 還在信誓旦旦地說:此類型的項目非微軟的ASP.NET MVC不可!

如何評價這些人呢?我隻想說:你們還是小點聲吧,小心遭人鄙視!

說到寫架構,我想還是有必要再說說我寫架構的原因:(引用我在部落格【用Asp.net寫自己的服務架構】中的原話)

自己寫架構的好處不在于能将它做得多強大,多完美,而是從寫架構的過程中,可以學到很多東西。

一個架構寫完了,不在乎要給多少人使用,而是自己感覺有沒有進步,這才是關鍵。

不管你信不信,那些喜歡說【非什麼什麼不可】的人,通常是從來不會寫架構的。

MyMVC的介紹還未結束,下篇部落格将會繼續,下篇部落格的重點在于UI部分的支援和實作, 這也正是MVC思想存在的必要性,當然也可以反映出MVC架構的核心價值。

說到這裡,我打算給下篇部落格做個預告:

MyMVC架構的後半部分在設計上主要展現了MVC這三者的關系,在設計時主要遵循了Martin Fowler大叔的總結: 從模型中分離表現和從視圖中分離控制器。

最終MyMVC對于UI部分支援的結果是:多個URL可以映射到一個Action,一個Action可以将結果指定給多個View來輸出。 也就是說:請求與View是一種多對多的關系,而中間的Controller隻是一個。 至于Model的傳回,可以由Controller根據運作的上下文條件給出不同的結果,同一個Model可以交給不同的View來顯示, 也可以傳回不同的Model,分别交給不同的View來顯示。

寫部落格真不容易,為了寫這篇部落格,我先要寫MyMVC架構以及準備示例代碼,再準備一些Visio圖,最後是文字部分,總共花了整整二個星期。 這還不包括前面二篇做為鋪墊的部落格:【細說 ASP.NET控制HTTP緩存】和【細說 HttpHandler 的映射過程】。 但是,每當看到自己寫的部落格在部落格園上擁有較高的【推薦數量】時,感覺寬慰了許多。 但願今天的部落格能受歡迎。

感謝 Amy(黃敏)同學為本文所做的校對工作,她已幫我找了好多處文字上的錯誤。

轉自:http://www.cnblogs.com/fish-li/archive/2012/02/12/2348395.html