Asp.net WebPages框架運行原理淺析


在Asp.net4和4.5中,新增了WebPages Framework,編寫頁面代碼使用了新的Razor語法,代碼更加的簡潔和符合Web標准,編寫方式更接近於PHP和以前的Asp,和使用WebForms這種模仿Windows Form編程方式有了很大不同,不再有大量控件和控件生成的大量不夠靈活的代碼,但是同樣可以使用Asp.net提供的大量類庫和功能,可以說WebPages框架融合了Asp、PHP和Asp.net的全部優點,又可使用C#和VB編程語言。一看到WebPages框架,我就馬上有了深入學習的興趣,因為它和WebForms相比立刻就會讓有完美主義情結的程序員們傾心。

但WebPages框架卻並沒有綁定Razor語法,它可以使用第三方的視圖引擎。WebPages和Razor也並沒有和Asp.net MVC具有必然的聯系。在VS2012中默認的網站模板里面多了”Asp.net網站(Razor v2)“,可以根據Razor語法創建WebPage。下圖說明了WebPages框架在Asp.net中的位置。

WebPages網站簡介

WebPages網站包含多個cshtml或vbhtml頁面,這些頁面中使用Razor模板語法,整個網站的文件都在一個文件夾中,bin目錄中有各種要用到的dll,沒有解決方案文件,解決方案文件在另外一個和網站同時創建的項目中,其中有packages目錄以管理WebPages網站需要用到的包。一個普通的cshtml頁代碼如下:

@{
    var db = Database.Open("StarterSite");
    var users = db.Query("Select * From UserProfile");
    var grid = new WebGrid(users);
}
<!DOCTYPE html>
<html>
    <head>
        <title></title>
    </head>
    <body>
        @grid.GetHtml()
    </body>
</html>

從中可以看到,這種編寫方式和PHP、Asp很相似,但WebPages身后卻是龐大的Asp.net類庫。

WebPages框架相關配置

在WebPages網站的web.config中並沒有什么特殊配置,在.net framework 4.0中的web.config中相關的配置如下:

<compilation>
    <assemblies>
        <remove assembly="System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        <add assembly="System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

    <add assembly="System.Web.WebPages.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
     </assemblies>
</compilation>

<httpHandlers>
<add path="*.cshtm" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
<add path="*.cshtml" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
<add path="*.vbhtm" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
<add path="*.vbhtml" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
</httpHandlers>

其中沒有相關buildProviders的配置也沒有相關httpModules的配置,httpHandlers的配置還將其映射到了HttpForbiddenHandler禁止訪問。在IIS或IIS Express中的配置中也只有Asp.net4.0的ISAPI的配置而沒有相關的httpModule。而WebForms在web.config中配置有相應的httpHandler和buildProvider。那么WebPages框架是如何運行的,其和WebForms在哪里產生了不同,WebPages框架是如何在Asp.net框架下實現的,這就需要進入WebPages框架進行探索。

WebPages框架自動運行過程

從web.config中的System.Web.WebPages.Deployment程序集開始,這個程序集dll有一個Asp.net4.0新增的特性PreApplicationStartMethodAttribute,這個特性配置了一個靜態方法以在程序啟動之前自動執行,特性如下:[assembly: PreApplicationStartMethod(typeof(PreApplicationStartCode), "Start")],查看PreApplicationStartCode類的Start方法,其調用了StartCore方法,StartCore又調用了LoadWebPages方法,其中實現的功能主要是獲取所有和WebPages框架相關的dll並得到這些dll上配置的PreApplicationStartMethodAttribute特性對應的啟動方法並全部執行,具有這個特性的dll有System.Web.WebPages、System.Web.WebPages.Razor和WebMatrix.WebData,我們主要關注前兩個。

private static void LoadWebPages(Version version)
{
    IEnumerable<Assembly> assemblies = Enumerable.Select<AssemblyName, Assembly>(AssemblyUtils.GetAssembliesForVersion(version), new Func<AssemblyName, Assembly>(null, (IntPtr) LoadAssembly));
    foreach (Assembly assembly in assemblies)
    {
        BuildManager.AddReferencedAssembly(assembly);
    }
    foreach (MethodInfo info in GetPreStartInitMethodsFromAssemblyCollection(assemblies))
    {
        info.Invoke(null, null);
    }
} 

在System.Web.WebPages.Razor程序集上的啟動方法代碼如下:

public static class PreApplicationStartCode
{
    // Fields
    private static bool _startWasCalled;

    // Methods
    public static void Start()
    {
        if (!_startWasCalled)
        {
            _startWasCalled = true;
            BuildProvider.RegisterBuildProvider(".cshtml", typeof(RazorBuildProvider));
            BuildProvider.RegisterBuildProvider(".vbhtml", typeof(RazorBuildProvider));
        }
    }
}

其注冊了cshtml和vbhtml文件對應的BuildProvider為RazorBuildProvider,即編譯Razor語法文件的提供程序。

在System.Web.WebPages程序集上的啟動方法代碼如下,這些啟動類的名字和方法是一樣的

public static class PreApplicationStartCode
{
    // Fields
    private static bool _startWasCalled;

    // Methods
    public static void Start()
    {
        if (!_startWasCalled)
        {
            _startWasCalled = true;
            WebPageHttpHandler.RegisterExtension("cshtml");
            WebPageHttpHandler.RegisterExtension("vbhtml");
            PageParser.EnableLongStringsAsResources = false;

        //此行代碼注冊了WebPageHttpModule            
       DynamicModuleUtility.RegisterModule(typeof(WebPageHttpModule));
ScopeStorage.CurrentProvider = new AspNetRequestScopeStorageProvider(); } } }

其中最重要的功能就是自動注冊了一個HttpModule,到此我們就可以知道WebPages頁面的編譯和處理已經有了着落了。接着查看WebPageHttpModule的代碼,這個httpmodule注冊處理了HttpApplication的PostResolveRequestCache,BeginRequest和EndRequest事件,這些代碼會在用戶請求cshtml和vbhtml頁面時觸發執行,在這個過程中WebPageHttpModule還會在WebPages網站首次啟動的時候調用System.Web.WebPages.ApplicationStartPage.ExecuteStartPage方法,在PostResolveRequestCache事件處理代碼中調用了WebPageRoute的方法,其中創建了處理頁面的WebPageHttpHandler類。

internal static void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
    HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
    new WebPageRoute().DoPostResolveRequestCache(context);
}
internal void DoPostResolveRequestCache(HttpContextBase context)
{
    if (!this.IsExplicitlyDisabled)
    {
        string pathValue = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + context.Request.PathInfo;
        ReadOnlyCollection<string> registeredExtensions = WebPageHttpHandler.GetRegisteredExtensions();
        WebPageMatch match = MatchRequest(pathValue, registeredExtensions, this.VirtualPathFactory, context, DisplayModeProvider.Instance);
        if (match != null)
        {
            context.Items[typeof(WebPageMatch)] = match;
            string path = "~/" + match.MatchedPath;
        //是否在WebPages網站的web.config中明確配置了webpages:Enabled
if (!WebPagesDeployment.IsExplicitlyDisabled(path)) { //創建WebPageHttpHandler IHttpHandler handler = WebPageHttpHandler.CreateFromVirtualPath(path); if (handler != null) { SessionStateUtil.SetUpSessionState(context, handler);
            //替換web.config中配置的HttpForbiddenHandler context.RemapHandler(handler); } } }
else { string extension = PathUtil.GetExtension(pathValue); foreach (string str4 in registeredExtensions) { if (string.Equals("." + str4, extension, StringComparison.OrdinalIgnoreCase)) { throw new HttpException(0x194, null); } } } } }

WebPageRoute還實現了WebPages網站頁面地址更加友好的功能,如不必帶cshtml和vbhtml后綴即可訪問相應頁面,可采用filename/category/id之類的地址訪問,通過添加xx.Mobile.cshtml即可自動實現切換到移動頁的功能等。到此HttpHandler創建完畢之后,就開始執行頁面,WebPages頁面的基類是System.Web.WebPages.WebPage。

如果我們在WebPages網站的web.config里面配置了webpages:Enabled為false,那么再次訪問cshtml或vbhtml時發現處理它們的是HttpForbiddenHandler。

<appSettings>
    <add key="webpages:Enabled" value="false"/>
</appSettings>
無法提供此類型的頁
<!DOCTYPE html>
<html>
    <head>
        <title>無法提供此類型的頁。</title>
        <meta name="viewport" content="width=device-width" />
        <style>
         body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;} 
         p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
         b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
         H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
         H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
         pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}
         .marker {font-weight: bold; color: black;text-decoration: none;}
         .version {color: gray;}
         .error {margin-bottom: 10px;}
         .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }
         @media screen and (max-width: 639px) {
          pre { width: 440px; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
         }
         @media screen and (max-width: 479px) {
          pre { width: 280px; }
         }
        </style>
    </head>

    <body bgcolor="white">

            <span><H1>“/”應用程序中的服務器錯誤。<hr width=100% size=1 color=silver></H1>

            <h2> <i>無法提供此類型的頁。</i> </h2></span>

            <font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">

            <b> 說明: </b>由於已明確禁止所請求的頁類型,無法對該類型的頁提供服務。擴展名“.cshtml”可能不正確。 &nbsp;&nbsp;請檢查以下的 URL 並確保其拼寫正確。
            <br><br>

            <b> 請求的 URL: </b>/s.cshtml<br><br>

            <hr width=100% size=1 color=silver>

            <b>版本信息:</b>&nbsp;Microsoft .NET Framework 版本:4.0.30319; ASP.NET 版本:4.0.30319.17929

            </font>

    </body>
</html>
<!-- 
[HttpException]: 禁止路徑“/s.cshtml”。
   在 System.Web.HttpForbiddenHandler.ProcessRequest(HttpContext context)
   在 System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   在 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
-->

至此,我們對WebPages框架的相關運行原理有了一個大概的了解。WebPages框架通過Asp.net4.0提供的PreApplicationStartMethodAttribute特性實現了HttpModule和BuildProvider注冊,而不是通常的web.config配置文件,本文介紹的過程並不是絕對的,因為這個特性是可以被Asp.net Runtime自動識別和運行的,例如只要你引用了System.Web.WebPages就會執行其上配置的注冊WebPageHttpModule的方法,其這些程序集上的啟動方法都有判斷機制防止重復執行。

和Asp.net MVC中的Razor視圖引擎的關系

Razor並沒有和MVC緊密耦合,其可以脫離MVC,也可以脫離WebPage框架。System.Web.MVC程序集上也有PreApplicationStartMethodAttribute特性,其中分別直接調用了System.Web.WebPages和System.Web.WebPages.Razor中的啟動類以注冊WebPageHttpModule和RazorBuildProvider,但MVC之中又繼承了System.Web.Razor中的WebPageRazorHost類實現了自己特有的MvcWebPageRazorHost,相當於增加了新的功能以和MVC配合。

正如MVC中有多種不同的視圖引擎一樣,WebPages框架也不一定要使用Razor引擎,我們可以實現自己的BuildProvider來定義WebPage語法和解析生成.Net程序集。

結語

個人認為WebPages框架在人們越來越注重Web標准和前端UI的情況下,拋棄了WebForms有些笨重和不透明的編程方式,具有極大的靈活性同時又可以利用Asp.net強大的類庫,后端的框架模型並沒有變,但卻讓Asp.net程序員有了和PHP、Asp類似的快速編程體驗。受夠了WebForms輸出代碼的臃腫的程序員們,趕快學習WebPages吧:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM