ASP.NET Core知多少系列:总体介绍及目录

Demo路径:GitHub-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 --version
2.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打开解决方案,如下图:

  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即可访问。

到这一步,我们就可以笃定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重新运行,运行结果如下图:

可以清晰的看到,定义的样式并未生效。从浏览器F12 Developer Tool中可以清晰的看到,无法请求demo.css样式文件。

到这里,也就抛出了本文所要解决的问题:如何开发独立通用的RPL?

如果RPL中无法引用项目中定义一些静态资源文件(CSS、JS、Image等),那RPL将无法有效的组织View。

4. Analyze

要想访问RPL中的静态资源文件,首先我们要弄明白.NET Core Web项目中wwwroot文件夹的资源是如何访问的。

这一切得从应用程序启动说起,为了方便查阅,使用Code Map将相关代码显示如下:

从中可以看出在构建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 = new
PhysicalFileProvider(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>
  1. 添加GenerateEmbeddedFilesManifest节点,指定生成内嵌资源清单。
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  1. 添加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,查看下其程序集清单:

从图中可以看出内嵌的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!

当然这也不是最好的解决方案,因为你肯定不想所有调用这个RPL的地方,添加这么几句代码,因为这段代码有很强的侵入性,且不可隔离变化。

5. Final Solution

  1. 编辑RPL.CommonUI.csproj文件,添加wwwroot为内嵌资源。
  <ItemGroup>
<EmbeddedResource Include="wwwroot\**\*" />
</ItemGroup>
  1. 添加GenerateEmbeddedFilesManifest节点,指定生成内嵌资源清单。
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  1. 添加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>
  1. 接下来添加CommonUIConfigureOptions.cs,定义如下:
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. 然后添加CommonUIServiceCollectionExtensions.cs,代码如下:
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. 修改RPL.Web启动类startup.cs,在services.AddMvc()之前添加services.AddCommonUI();即可。

  2. CTRL+F5重新运行,我们发现H1被成功设置为红色,检查发现demo.css也能正确被请求,检查network也可以看到其Request URL为:https://localhost:44379/css/demo.css



6. Case Study

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

这个链接是一个进阶demo,演示了如何使用RPL去创建可重用的邮件模板,感兴趣的不妨一看。

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的文件系统[4]:由EmbeddedFileProvider构建的内嵌(资源)文件系统

Razor Page Library:开发独立通用RPL(内嵌wwwroot资源文件夹)的更多相关文章

  1. .NET Core的文件系统[4]:由EmbeddedFileProvider构建的内嵌(资源)文件系统

    一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以统一的编程方式来读取内嵌于某个程序集中的资源文件,不过在这之前我们必须知道如何将一个项目文 ...

  2. 由EmbeddedFileProvider构建的内嵌(资源)文件系统

    由EmbeddedFileProvider构建的内嵌(资源)文件系统 一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以统一的编程方式来读取 ...

  3. [小程序开发] 微信小程序内嵌网页web-view开发教程

    为了便于开发者灵活配置小程序,微信小程序开放了内嵌网页能力.这意味着小程序的内容不再局限于pages和large,我们可以借助内嵌网页丰富小程序的内容.下面附上详细的开发教程(含视频操作以及注意事项) ...

  4. Jetty 开发指南:Jetty 内嵌开发

    Jetty的口号是“不要在Jetty中部署你的应用程序,在你的应用程序中部署Jetty!” 这意味着,作为将应用程序捆绑为要部署在Jetty中的标准WAR的替代方案,Jetty旨在成为一个软件组件,可 ...

  5. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:内嵌下拉菜单的按钮组

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. SpringBoot系列四:SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清 ...

  7. SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)

    1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清楚的认识到以下的问题,在实际的项目开发之中,尤其是 Java 的 MVC ...

  8. [golang]Go内嵌静态资源go-bindata的安装及使用

    使用 Go 开发应用的时候,有时会遇到需要读取静态资源的情况.比如开发 Web 应用,程序需要加载模板文件生成输出的 HTML.在程序部署的时候,除了发布应用可执行文件外,还需要发布依赖的静态资源文件 ...

  9. golang1.16内嵌静态资源指南

    今天是万圣节,也是golang1.16新特性冻结的日子.不得不说自从go2路线发布之后golang新特性的迭代速度也飞速提升,1.16中有相当多的重要更新,包括io标准库的重构,语言内置的静态资源嵌入 ...

随机推荐

  1. 为什么margin-top不是作用于父元素【margin外边距合并问题】

    coding时发现margin-top居然没作用于本元素上,而是作用到了父元素上. 原来是margin外边距合并导致的.以下是网上搬运来的知识: margin外边距合并详解:外边距合并现象在网页布局中 ...

  2. TimeStamp

    private void Form1_Load(object sender, EventArgs e) { textBox1.Text= GenerateTimeStamp(System.DateTi ...

  3. Redis适用于高并发的递增、递减功能

    递增指令:incr(默认从0开始) 递减指令:decr(默认从0开始,递减会出现负数,这点跟memcache不一样,mc到0) 如下: 附上shardedJedisPool和JedisCluster的 ...

  4. Java浮点值拒绝服务漏洞危害分析

    By 空虚浪子心 http://www.inbreak.net/ JAVA出了漏洞,CVE-2010-4476,会导致拒绝服务攻击.大家能从公告上,看到这样一段代码,挺长的.意思是只有开发人员写出这样 ...

  5. 【学习笔记】【C语言】结构体

    1.定义结构体变量的3种方式 1> 先定义类型,再定义变量(分开定义) struct Student {    int age; }; struct Student stu;  2> 定义 ...

  6. nyoj重建二叉树(不真的建立)

      感觉c++很陌生啊 题目很简单,给你一棵二叉树的后序和中序序列,求出它的前序序列(So easy!). 输入 输入有多组数据(少于100组),以文件结尾结束.每组数据仅一行,包括两个字符串,中间用 ...

  7. mac中的xampp配置xdebug

    [xdebug] zend_extension=/Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20121212/ ...

  8. Android-管理Activity生命周期 -暂停和恢复一个Activity

    在正常的使用app时,前台的activity有时候会被可见的组件阻塞导致activity暂停.比如,当打开一个半透明的activity(就像打开了一个对话框),之前的activity就会暂停.只要ac ...

  9. iOS动画学习 -隐式动画

    事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.你并不需要在Core Animation中手动打开动画,但是你需要明确地关闭它,否则它会一直存在. 当你改变 ...

  10. Android开发学习之路--网络编程之xml、json

    一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...