1. 程式人生 > >Razor Page Library:開發獨立通用RPL(內嵌wwwroot資原始檔夾)

Razor Page Library:開發獨立通用RPL(內嵌wwwroot資原始檔夾)

Demo路徑:https://github.com/yanshengjie/RPL.Demo

1. Introduction

Razor Page Library 是ASP.NET Core 2.1引入的新類庫專案,屬於新特性之一,用於建立通用頁面公用類庫。也就意味著可以將多個Web專案中通用的Web頁面提取出來,封裝成RPL,以進行程式碼重用。
官方文件Create reusable UI using the Razor Class Library project in ASP.NET Core中,僅簡單介紹瞭如何建立RPL,但要想開發出一個獨立通用的RPL遠遠沒有那麼簡單,容我娓娓道來。

2. Hello RPL

老規矩,從Hello World 開始,我們建立一個Demo專案。
記住開始之前請確認已安裝.NET Core 2.1 SDK!!!
我們這次使用命令列來建立專案:

>dotnet --version2.1.300>dotnet new razorclasslib --name RPL.CommonUI
已成功建立模板“Razor Class Library”。

正在處理建立後操作...正在 RPL.CommonUI\RPL.CommonUI.csproj 上執行 "dotnet restore"...
  正在還原 F:\Coding\Demo\RPL.CommonUI\RPL.CommonUI.csproj 的包...
 正在生成 MSBuild 檔案 F:\Coding\Demo\RPL.CommonUI\obj\RPL.CommonUI.csproj.nuge t.g.props。  正在生成 MSBuild 檔案 F:\Coding\Demo\RPL.CommonUI\obj\RPL.CommonUI.csproj.nuge t.g.targets。  F:\Coding\Demo\RPL.CommonUI\RPL.CommonUI.csproj 的還原在 1.34 sec 內完成。 還原成功。 >dotnet new mvc --name RPL.Web 已成功建立模板“ASP.NET Core Web App (Model-View-Controller)”。 此模板包含非 Microsoft 的各方的技術,有關詳細資訊,請參閱 https://aka.ms/aspnetc ore-template-3pn-210
。 正在處理建立後操作...正在 RPL.Web\RPL.Web.csproj 上執行 "dotnet restore"...  正在還原 F:\Coding\Demo\RPL.Web\RPL.Web.csproj 的包...  正在生成 MSBuild 檔案 F:\Coding\Demo\RPL.Web\obj\RPL.Web.csproj.nuget.g.props 。  正在生成 MSBuild 檔案 F:\Coding\Demo\RPL.Web\obj\RPL.Web.csproj.nuget.g.target s。  F:\Coding\Demo\RPL.Web\RPL.Web.csproj 的還原在 2 sec 內完成。 還原成功。 >dotnet new sln --name RPL.Demo 已成功建立模板“Solution File”。 >dotnet sln RPL.Demo.sln add RPL.CommonUI/RPL.CommonUI.csproj 已將專案“RPL.CommonUI\RPL.CommonUI.csproj”新增到解決方案中。 >dotnet sln RPL.Demo.sln add RPL.Web/RPL.Web.csproj 已將專案“RPL.Web\RPL.Web.csproj”新增到解決方案中。

建立完畢後,雙擊RPL.Demo.sln開啟解決方案,如下圖:

640?wx_fmt=png

  1. 修改Page1.cshtml,body內新增<h1>This is from CommonUI.Page1</h1>

  2. RPL.Web新增引用專案【RPL.CommonUI】

  3. 設定RPL為啟動專案。

  4. CTRL+F5執行。

我們觀察到RPL.CommonUI中預置了一個Razor Page,因為Razor Page是基於檔案系統路由,所以直接https://localhost:<port>/myfeature/page1即可訪問。
640?wx_fmt=png

到這一步,我們就可以篤定RPL正確生效。

3. Keep Going

以上只是簡單的HTML頁面,如果要想加以潤色,就需要寫CSS來處理。
兩種處理方式:

  1. 使用內聯樣式

  2. 引用外部樣式檔案

內聯樣式,很簡單,就不加以贅述。
我們來定義樣式檔案來處理。仿照RPL.Web專案,建立一個wwwroot根目錄,然後再新增一個css資料夾,再新增一個demo.css的樣式檔案。

h1 {    color: red;
}

然後將demo.css引用新增到page1.cshtml中。

<head>
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="~/css/demo.css" />
    <title>Page1</title></head>

CTRL+F5重新執行,執行結果如下圖:
640?wx_fmt=png

可以清晰的看到,定義的樣式並未生效。從瀏覽器F12 Developer Tool中可以清晰的看到,無法請求demo.css樣式檔案。
到這裡,也就丟擲了本文所要解決的問題:如何開發獨立通用的RPL?
如果RPL中無法引用專案中定義一些靜態資原始檔(CSS、JS、Image等),那RPL將無法有效的組織View。

4. Analyze

要想訪問RPL中的靜態資原始檔,首先我們要弄明白.NET Core Web專案中wwwroot資料夾的資源是如何訪問的。
這一切得從應用程式啟動說起,為了方便查閱,使用Code Map將相關程式碼顯示如下:
640?wx_fmt=png

從中可以看出在構建WebHost的業務邏輯中會去初始化IHostingEnvironment物件。該物件主要用來描述應用程式執行的web宿主環境的相關資訊,主要包含以下幾個屬性:

string EnvironmentName { get; set; }string ApplicationName { get; set; }string WebRootPath { get; set; }
IFileProvider WebRootFileProvider { get; set; }string ContentRootPath { get; set; }
IFileProvider ContentRootFileProvider { get; set; }

從上圖的註釋程式碼中可以看到,其初始化邏輯正是去指定WebRootPathWebRootFileProvider
如果我們在應用程式未手動通過webHostBuilder.UseWebRoot("your web root path");指定自定義的Web Root路徑,那麼將會預設指定為wwwroot資料夾。
同時注意下面這段程式碼:

hostingEnvironment.WebRootFileProvider = newPhysicalFileProvider(hostingEnvironment.WebRootPath);

其指定的IFileProvider的型別為PhysicalFileProvider
到這裡,是不是就豁然開朗了,Web 應用啟動時,指定的WebRootFileProvider僅僅映射了Web應用的wwwroot目錄,自然是訪問不了我們RPL專案指定的wwwroot目錄啊。

到這裡,其實我們離問題就很近了。但是隻要指定了WebRootFileProvider就可以訪問WebRoot目錄的資源了嗎?並不是。

我們知道,ASP.NET Core是通過由一系列中介軟體組裝而成的請求管道來處理請求的。不管是View檢視也好,還是靜態資原始檔也好,都是通過Http Request來請求的。HTTP Request流入請求管道後,根據請求型別,不同的中介軟體負責處理不同的請求。那對於靜態資原始檔,ASP.NET Core中是藉助StaticFileMiddleware中介軟體來處理的。這也就是為什麼在啟動類StartupConfigure方法中需要指定app.UseStaticFiles();來啟用StaticFileMiddleware中介軟體。

在ASP.NET Core 官方文件中Static files in ASP.NET Core,介紹瞭如何訪問自定義目錄的靜態資原始檔。

如果需要訪問自定義路徑目錄的資源,需要新增類似以下程式碼:

app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
        RequestPath = "/StaticFiles"
    });

但這似乎並不能滿足我們的需求。Why?看標題,開發獨立通用的RPL。怎麼理解獨立通用?也就意味著RPL中的資原始檔最好能夠通過程式集打包。這樣才能完全獨立。否則,在釋出RPL時,還需要輸出靜態資原始檔,顯然增加了使用的難度。而如何將資原始檔打包程序序集呢?——內嵌資源。

5. Embedded Resource

一個程式集主要由兩種型別的檔案構成,它們分別是承載IL程式碼的託管模組檔案和編譯時內嵌的資原始檔。那在.NET Core中如何定義內嵌資源呢?

  1. 編輯RPL.CommonUI.csproj檔案,新增wwwroot為內嵌資源。

      <ItemGroup><EmbeddedResource Include="wwwroot\**\*" />
      </ItemGroup>
  2. 新增GenerateEmbeddedFilesManifest節點,指定生成內嵌資源清單。

    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  3. 新增Microsoft.Extensions.FileProviders.EmbeddedNuget包引用。

修改完後的RPL.CommonUI.csproj,如下所示:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.0" />
    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.0" />
  </ItemGroup>
  <ItemGroup>
    <EmbeddedResource Include="wwwroot\**\*" />
  </ItemGroup></Project>

我們用ildasm.exe反編譯RPL.CommonUI.dll,檢視下其程式集清單:

640?wx_fmt=png

從圖中可以看出內嵌的demo.css檔案,是以{程式集名稱}.{檔案路徑}命名的。

那內嵌資源如何訪問呢?可以藉助EmbeddedFileProvider,我們仿照上面的例子,在Startup.csConfigure方法中新增以下程式碼:

app.UseStaticFiles();var dllPath = Path.Join(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "RPL.CommonUI.dll");
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new ManifestEmbeddedFileProvider(Assembly.LoadFrom(dllPath), "wwwroot")
});

CTRL+F5,執行。Perfect!
640?wx_fmt=png

當然這也不是最好的解決方案,因為你肯定不想所有呼叫這個RPL的地方,新增這麼幾句程式碼,因為這段程式碼有很強的侵入性,且不可隔離變化。

5. Final Solution

  • 編輯RPL.CommonUI.csproj檔案,新增wwwroot為內嵌資源。

      <ItemGroup><EmbeddedResource Include="wwwroot\**\*" />
      </ItemGroup>
  • 新增GenerateEmbeddedFilesManifest節點,指定生成內嵌資源清單。

    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  • 新增Microsoft.AspNetCore.StaticFilesMicrosoft.Extensions.FileProviders.EmbeddedNuget包引用。

修改完後的RPL.CommonUI.csproj,如下所示:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.0" />
    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.0" />
  </ItemGroup>
  <ItemGroup>
    <EmbeddedResource Include="wwwroot\**\*" />
  </ItemGroup></Project>
  • 接下來新增CommonUIConfigureOptions.cs,定義如下:

640?wx_fmt=png

  • 然後新增CommonUIServiceCollectionExtensions.cs,程式碼如下:

640?wx_fmt=png

  • 修改RPL.Web啟動類startup.cs,在services.AddMvc()之前新增services.AddCommonUI();即可。

  • CTRL+F5重新執行,我們發現H1被成功設定為紅色,檢查發現demo.css也能正確被請求,檢查network也可以看到其Request URL為:https://localhost:44379/css/demo.css
    640?wx_fmt=png
    640?wx_fmt=png

  1. Static files in ASP.NET Core

  2. File Providers in ASP.NET Core

  3. ManifestEmbeddedFileProvider Class

  4. Make it easier to use static assets that are part of a RCL project

  5. .NET Core的檔案系統[4]:由EmbeddedFileProvider構建的內嵌(資源)檔案系統

相關文章: 

原文地址: https://www.cnblogs.com/sheng-jie/p/9165547.html

.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com

640?wx_fmt=jpeg