.Net 472&6.0 Razor編譯時的小差異

2023-06-28 09:01:00

前言

幾個月前在進行著.Net 472到6.0的升級,複用原有程式碼,在對Razor進行遷移中,發現原執行正常的程式碼,卻存在報錯,深入研究發現是Core下對Razor編譯有一些變動。

問題復現

472 建立檢視

新建.Net Framework下Mvc,增加一個簡單檢視如下。

@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorDemo472.Web</title>
    @{
        var headContent = "headContent";
    }
</head>
<body>
    <h1>@headContent</h1>
</body>
</html>

暫不論這個變數定義的位置合不合適,或許應該全域性定義,但是總歸在472下是正常的,可執行的。

6.0 建立檢視

新建Asp.Net Core Mvc專案,同樣使用如上簡單檢視

@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorDemo6.Web</title>
    @{
        var headContent = "headContent";
    }
</head>
<body>
    <h1>@headContent</h1>
</body>
</html>

如果只是這樣,不做任何設定,編譯時候就會報錯。

6.0 啟用執行時編譯

因為整體遷移檢視太多,很多檢視沒有如上的寫法(head中的變數在body中使用),並且在472下執行正常,也沒有改動Razor檢視上的程式碼。為了加快遷移後啟動的速度,不至於編譯太長時間,另外也想著檢視內容能夠實時更新不用關了再重開,我們把構建和釋出時編譯Razor給關閉了。


此處在Demo中使用Razor執行時編譯

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews()
    .AddRazorRuntimeCompilation();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.MapDefaultControllerRoute();
app.Run();

啟動專案,請求頁面後便是在執行時發現頁面報錯。

當區域性頁面(整個遷移過程中只發現兩個頁面存在如上寫法)出現如上錯誤後,開始找根本問題所在。

問題分析

6.0因關閉了構建和釋出編譯,使得Razor中出現程式碼報錯無法知道,如開啟構建和釋出編譯,則會提示程式碼報錯,但為何同樣的程式碼,472可用,6.0會報錯?

先開啟6.0下構建和釋出編譯,對比下472和6.0的差異在何處。

472 IL分析

將472啟動後存取頁面,再找到對應檢視dll,如下是復現時的dll程式碼,Razor檢視沒有分塊編譯,整個變數時可以在head和body共用的。

6.0 IL分析

6.0專案改造一下,開啟構建與釋出編譯,移除Razor的執行時編譯,更改一下檢視內容,以避免變數構建時直接報錯。

@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    @{
        var headContent = "headContent";
    }
    <title>@ViewData["Title"] - RazorDemo6.Web @headContent</title>
</head>
<body>
    @{
        var headContent1 = "headContent1";
    }
    <h1>@headContent1</h1>
</body>
</html>

專案構建,從生成的專案dll中找到程式碼,Asp.Net Core Razor檢視編譯時會按照tag分塊,head和body分開,以至於變數不能在兩個tag間共用。


原始碼分析

472 原始碼分析

  1. 首先通過ViewEngine查詢檢視檔案。

  2. 再進入到對應的RazorViewEngine(繼承父類別VirtualPathProviderViewEngine)中,存在CreateView方法,返回IView,其實現即RazorView(繼承父類別BuildManagerCompiledView)。

  3. 開始準備ViewContext和output, 呼叫的是RazorView中的Render方法。

  4. Rende中呼叫BuildManager.GetCompliedType(ViewPath),完成將cshtml編譯成動態檢視類。再執行ViewPageActivator.Create方法將動態生成的檢視類寫入到資料夾中(注意此處是不存在則生成,存在則使用現有的)。

從其內部實現中也可看到,輸出的程式集以App_Web_為字首。

如下簡便描述下BuilderManager內部呼叫過程。

// BuilderManager.cs 
GetCompiledType()
   GetVirtualPathObjectFactory()
      GetVPathBuildResultWithAssert()
        GetVPathBuildResultWithNoAssert()
          GetVPathBuildResultInternal()
            CompilationLock.GetLock()
            CompileWebFile() //Core
              buildResult = buildProvider.GetBuildResult(results); //build
                 CreateBuildResult():BuildProvider.cs
            CompilationLock.ReleaseLock()

6.0 原始碼分析

當使用執行時編譯時,會呼叫擴充套件方法註冊到服務容器中。

builder.Services.AddControllersWithViews()
    .AddRazorRuntimeCompilation();

該擴充套件方法AddRazorRuntimeCompilation中會完成Razor轉換所需的一些服務註冊。

在這其中會範例化好RazorProjectEngine(原RazorTemplateEngine更名)。當進行檢視編譯時,會將檔案載入,再由RazorProjectEngine負責將其語法分析,轉換,構建成文字,最終生成程式碼並生成類。

再有了TagHelper加持後,此處生成的cSharpDocument.GeneratedCode便是會按照TagHelperScope分塊。

而生成這些tagHelper的地方則處於ViewComponentTagHelperTargetExtension.cs中。



控制檯中使用Razor渲染

當直接使用Razor模板引擎時,並不會按照tag分塊(或是說沒接入tag功能),簡單使用一個例子來做對比。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    @{
        var headContent = "headContent";
    }
    <title>RazorDemo6.Web @headContent</title>
</head>
<body>
    @{
        var headContent1 = "headContent1";
    }
    <h1>@headContent1</h1>
</body>
</html>

範例化RazorProjectEngine,載入檔案,直接輸出生成的程式碼。

using Microsoft.AspNetCore.Razor.Language;

var project = RazorProjectFileSystem.Create(Directory.GetCurrentDirectory());
var engine = RazorProjectEngine.Create(RazorConfiguration.Default, project, null);
var file = project.GetItem("Index.cshtml");
var codeDocument = engine.Process(file);
var code = codeDocument.GetCSharpDocument().GeneratedCode;
Console.WriteLine(code);
Console.ReadLine();

沒有了TagHelper後,生成的類中不會將head和body分塊。而是隻按照c#程式碼和html分開。

// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(Razor.Template), @"default", @"/Index.cshtml")]
namespace Razor
{
    #line hidden
    [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"755e7386ecf54079dc3cddaaeb531c72eabc1d9a", @"/Index.cshtml")]
    public class Template
    {
        #pragma warning disable 1998
        public async override global::System.Threading.Tasks.Task ExecuteAsync()
        {
            WriteLiteral("<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"utf-8\" />\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n");
#nullable restore
#line 6 "C:\Projects\RazorProjectEngine\RazorLanguage.Terminal\bin\Debug\net7.0\Index.cshtml"
        var headContent = "headContent";
#line default
#line hidden
#nullable disable
            WriteLiteral("    <title>RazorDemo6.Web ");
#nullable restore
#line 9 "C:\Projects\RazorProjectEngine\RazorLanguage.Terminal\bin\Debug\net7.0\Index.cshtml"
                     Write(headContent);
#line default
#line hidden
#nullable disable
            WriteLiteral("</title>\r\n</head>\r\n<body>\r\n");
#nullable restore
#line 12 "C:\Projects\RazorProjectEngine\RazorLanguage.Terminal\bin\Debug\net7.0\Index.cshtml"
        var headContent1 = "headContent1";
#line default
#line hidden
#nullable disable
            WriteLiteral("    <h1>");
#nullable restore
#line 15 "C:\Projects\RazorProjectEngine\RazorLanguage.Terminal\bin\Debug\net7.0\Index.cshtml"
   Write(headContent1);
#line default
#line hidden
#nullable disable
            WriteLiteral("</h1>\r\n</body>\r\n</html>");
        }
        #pragma warning restore 1998
    }
}
#pragma warning restore 1591

參考

  1. https://www.cnblogs.com/artech/archive/2012/09/04/razor-view-engine-01.html
  2. https://www.cnblogs.com/artech/archive/2012/09/05/razor-view-engine-02.html
  3. https://juejin.cn/post/7130956013242417166
  4. https://github.com/dotnet/aspnetcore/tree/cd9340856ed85215a911c97c44d52373f6cba2f9/src/Razor/Microsoft.AspNetCore.Razor.Language/src

2023-06-28,望技術有成後能回來看見自己的腳步