幾個月前在進行著.Net 472到6.0的升級,複用原有程式碼,在對Razor進行遷移中,發現原執行正常的程式碼,卻存在報錯,深入研究發現是Core下對Razor編譯有一些變動。
新建.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下是正常的,可執行的。
新建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>
如果只是這樣,不做任何設定,編譯時候就會報錯。
因為整體遷移檢視太多,很多檢視沒有如上的寫法(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啟動後存取頁面,再找到對應檢視dll,如下是復現時的dll程式碼,Razor檢視沒有分塊編譯,整個變數時可以在head和body共用的。
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間共用。
首先通過ViewEngine查詢檢視檔案。
再進入到對應的RazorViewEngine(繼承父類別VirtualPathProviderViewEngine)中,存在CreateView方法,返回IView,其實現即RazorView(繼承父類別BuildManagerCompiledView)。
開始準備ViewContext和output, 呼叫的是RazorView中的Render方法。
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()
當使用執行時編譯時,會呼叫擴充套件方法註冊到服務容器中。
builder.Services.AddControllersWithViews()
.AddRazorRuntimeCompilation();
該擴充套件方法AddRazorRuntimeCompilation中會完成Razor轉換所需的一些服務註冊。
在這其中會範例化好RazorProjectEngine(原RazorTemplateEngine更名)。當進行檢視編譯時,會將檔案載入,再由RazorProjectEngine負責將其語法分析,轉換,構建成文字,最終生成程式碼並生成類。
再有了TagHelper加持後,此處生成的cSharpDocument.GeneratedCode便是會按照TagHelperScope分塊。
而生成這些tagHelper的地方則處於ViewComponentTagHelperTargetExtension.cs中。
當直接使用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
2023-06-28,望技術有成後能回來看見自己的腳步