1. Introduction

Razor Page Library is a new class library project introduced by ASP.NET Core 2.1. It is one of the new features for creating a common page public class library. This means that Web pages that are common to multiple Web projects can be extracted and packaged into RPLs for code reuse.
The official document
Create reusable UI using the Razor Class Library project in ASP.NET Core

, only a brief introduction to how to create RPL, but to develop a stand-alone RPL is far from simple, let me know.

2. Hello RPL

Old rules, starting with Hello World, we create a Demo project.
Remember to confirm that you have installed the
.NET Core 2.1 SDK

before you start ! ! !
We use the command line this time to create a project:

>dotnet --version
2.1.300
>dotnet new razorclasslib --name RPL.CommonUI

>dotnet new mvc --name RPL.Web

>dotnet new sln --name RPL.Demo

>dotnet sln RPL.Demo.sln add RPL.CommonUI/RPL.CommonUI.csproj

>dotnet sln RPL.Demo.sln add RPL.Web/RPL.Web.csproj


Once created, double-click RPL.Demo.sln to open the solution, as shown below:

ASP.NET_Core__basic_series(11)_Razor_Page_Library_1.png

  1. Modify Page1.cshtml, add inside the body<h1>This is from CommonUI.Page1</h1>
  2. RPL.Web adds reference project [RPL.CommonUI]
  3. Set RPL to start the project.
  4. CTRL+F5 runs.

We observed that a Razor Page is preset in RPL.CommonUI, because Razor Page is based on file system routing, so it https://localhost:<port>/myfeature/page1can be accessed directly .

ASP.NET_Core__basic_series(11)_Razor_Page_Library_2.png

At this point, we can be sure that the RPL will take effect correctly.

3. Keep Going

The above is just a simple HTML page. If you want to polish it, you need to write CSS to handle it.
Two treatment methods:

  1. Use inline styles
  2. Reference external style file

Inline styles are very simple and will not be described.
Let’s define the style file to handle. Model the RPL.Web project, create a wwwroot root directory, then add a css folder, and then add a demo.css style file.

h1 {
    color: red;
}

Then add the demo.css reference to page1.cshtml.

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

CTRL+F5 re-run, the results are as follows:

ASP.NET_Core__basic_series(11)_Razor_Page_Library_3.png

It can be clearly seen that the defined style does not take effect. It is clear from the browser F12 Developer Tool that the demo.css style file cannot be requested.
Here, it also throws the problem to be solved in this article: How to develop a stand-alone RPL?
If RPL cannot reference some static resource files (CSS, JS, Image, etc.) defined in the project, then RPL will not be able to effectively organize the View. .

4. Analyze

To access static resource files in RPL, we first need to understand how the resources of the wwwroot folder in the .NET Core Web project are accessed.
This all starts with the application launch. For easy reference, use Code Map to display the relevant code as follows:

ASP.NET_Core__basic_series(11)_Razor_Page_Library_4.png

Program.cs

It can be seen that the IHostingEnvironmentobject is initialized in the business logic of building WebHost . This object is mainly used to describe the information about the web hosting environment in which the application runs. It mainly includes the following attributes:

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

As you can see from the comment code in the above figure, its initialization logic is just to specify the WebRootPathsum WebRootFileProvider.
If we do not manually pass the webHostBuilder.UseWebRoot(“your web root path”);specified custom web root path in the application , it will be specified as a wwwrootfolder by default .
Also note the following code:

hostingEnvironment.WebRootFileProvider = new
PhysicalFileProvider(hostingEnvironment.WebRootPath);

Its specified IFileProvidertype is PhysicalFileProvider.
At this point, is it suddenly clear, when the web application starts, the specified WebRootFileProvideronly maps the web application’s wwwroot directory, naturally it is impossible to access the wwwroot directory specified by our RPL project.

Here, in fact, we are very close to the problem. But WebRootFileProvidercan you access the resources of the WebRoot directory as long as you specify it? No.

We know that ASP.NET Core handles requests through a request pipeline assembled from a series of middleware. Whether it is a View view or a static resource file, it is requested through Http Request. After the HTTP Request flows into the request pipeline, different middleware is responsible for handling different requests depending on the type of request. For static resource files, ASP.NET Core is StaticFileMiddlewarehandled by means of middleware. This is why you need to specify to enable middleware Startupin the Configuremethod of starting the class .app.UseStaticFiles();StaticFileMiddleware

In the ASP.NET Core official documentation,
Static files in ASP.NET Core

, describes how to access static resource files for custom directories.

If you need to access a resource in a custom path directory, you need to add something like this:

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

But this does not seem to meet our needs. Why? Look at the title and develop a stand-alone RPL. How to understand independent universal? This means that the resource files in the RPL are best packaged through the assembly. This will be completely independent. Otherwise, when the RPL is released, it is also necessary to output a static resource file, which obviously increases the difficulty of use. And how to package resource files into assemblies? – Embedded resources.

5. Embedded Resource

An assembly consists mainly of two types of files: managed module files that carry IL code and resource files that are embedded at compile time. How do you define embedded resources in .NET Core?

  1. Edit the RPL.CommonUI.csproj file and add wwwroot as an embedded resource.
  <ItemGroup>
    <EmbeddedResource Include="wwwroot\**\*" />
  </ItemGroup>

  1. Add a GenerateEmbeddedFilesManifestnode to specify a list of built-in resources.
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>

  1. Add a Microsoft.Extensions.FileProviders.EmbeddedNuget package reference.

After the modification, RPL.CommonUI.csproj is as follows:

<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>

We decompile RPL.CommonUI.dll with ildasm.exe and check out its assembly list:

ASP.NET_Core__basic_series(11)_Razor_Page_Library_5.png

Manifest

As you can see from the figure, the embedded demo.css file is named after {assembly name}.{file path}.

How do you access the embedded resources? With the help EmbeddedFileProviderof the above example, we add the following code to Startup.csthe Configuremethod:

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, run. Perfect!

ASP.NET_Core__basic_series(11)_Razor_Page_Library_6.png

Of course, this is not the best solution, because you definitely don’t want to call all the RPL places, add a few lines of code, because this code is very invasive and can not be isolated.

5. Final Solution

  1. Edit the RPL.CommonUI.csproj file and add wwwroot as an embedded resource.
  <ItemGroup>
    <EmbeddedResource Include="wwwroot\**\*" />
  </ItemGroup>

  1. Add a GenerateEmbeddedFilesManifestnode to specify a list of built-in resources.
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>

  1. Add Microsoft.AspNetCore.StaticFilesand Microsoft.Extensions.FileProviders.EmbeddedNuget package references.

After the modification, RPL.CommonUI.csproj is as follows:

<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>

  1. Add CommonUIConfigureOptions.csit next , defined as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using System;

namespace RPL.CommonUI
{
    internal class CommonUIConfigureOptions: IPostConfigureOptions<StaticFileOptions>
    {
        public CommonUIConfigureOptions(IHostingEnvironment environment)
        {
            Environment = environment;
        }
        public IHostingEnvironment Environment { get; }

        public void PostConfigure(string name, StaticFileOptions options)
        {
            name = name ?? throw new ArgumentNullException(nameof(name));
            options = options ?? throw new ArgumentNullException(nameof(options));

            // Basic initialization in case the options weren't initialized by any other component
            options.ContentTypeProvider = options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
            if (options.FileProvider == null && Environment.WebRootFileProvider == null)
            {
                throw new InvalidOperationException("Missing FileProvider.");
            }

            options.FileProvider = options.FileProvider ?? Environment.WebRootFileProvider;

            // Add our provider
            var filesProvider = new ManifestEmbeddedFileProvider(GetType().Assembly, "wwwroot");
            options.FileProvider = new CompositeFileProvider(options.FileProvider, filesProvider);
        }
    }
}


  1. Then add CommonUIServiceCollectionExtensions.cs, the code is as follows:
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;

namespace RPL.CommonUI
{
    public static class CommonUIServiceCollectionExtensions
    {
        public static void AddCommonUI(this IServiceCollection services)
        {
            services.ConfigureOptions(typeof(CommonUIConfigureOptions));
        }
    }
}


  1. Modify the RPL.Web startup class startup.cs and services.AddMvc()add it before services.AddCommonUI();.
  2. CTRL+F5 re-run, we found that H1 was successfully set to red, check that demo.css can also be correctly requested, check the network can also see its Request URL is:
    https://localhost:44379/css/demo.css

    ASP.NET_Core__basic_series(11)_Razor_Page_Library_7.png

    ASP.NET_Core__basic_series(11)_Razor_Page_Library_8.png

    Request URL

6. Case Study


Demonstrate how to use Razor class library to create reusable email template.

This link is an advanced demo that demonstrates how to use RPL to create reusable mail templates.

7. References


  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 file system [4]: ​​Embedded (resource) file system built by EmbeddedFileProvider

Orignal link:https://www.jianshu.com/p/b6294c08484c