现象

在 ASP.NET Core MVC 中,当在页面中传递了一个包含中文字符串到页面的时候,页面的显示是正常的,但是如果查看页面源码,却看不到中文,变成了一串编码之后的内容。

例如,在页面中直接定义一个含有中文内容的字符串,然后在页面中显示出来。

@{
ViewData["Title"] = "Home Page";
string world = "世界";
} <div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>你好,@world。</p>
</div>

运行之后,可以看到页面是正常的

但是在查看页面源码的时候,中文不见了。

<p>你好,世界。</p>

原因就是字符串的内容是通过代码进行编码之后输出的。

分析

在 asp.net core 中,基于防范 xss 攻击的安全考虑,默认将所有非基本字符(U+0000..U+007F)的字符进行编码。因此基本除了英文字母那一部分,其他的全被编码了。

这个控制来源于 HtmlEncoder 中的 UnicodeRange 被设置成 UnicodeRanges.BasicLatin。

编码使用了 HtmlEncoder 这个类。它位于项目 System.Text.Encodings.Web at GitHub 中,默认它使用了 DefaultHtmlEncoder 进行编码,但是它基于拉丁字符进行编码。下面是源代码片段:

    internal sealed class DefaultHtmlEncoder : HtmlEncoder
{
private readonly AllowedCharactersBitmap _allowedCharacters;
internal static readonly DefaultHtmlEncoder Singleton = new DefaultHtmlEncoder(new TextEncoderSettings(UnicodeRanges.BasicLatin));

以及:

    public abstract class HtmlEncoder : TextEncoder
{
/// <summary>
/// Returns a default built-in instance of <see cref="HtmlEncoder"/>.
/// </summary>
public static HtmlEncoder Default
{
get { return DefaultHtmlEncoder.Singleton; }
}

点击这里 查看完全的源代码。

解决方案

配置将 UnicodeRange 范围放宽。我们可以通过将编码器替换为支持中文的 Unicode 编码器来解决它。

在 ASP.NET Core 中,各种服务是通过依赖注入来提供服务的。所以,我们可以通过调整容器中注册的服务来解决这个问题。

源代码中其实注入了一个 HtmlEncoder 来处理编码问题。

public DefaultHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache)
{
}

源码见:https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.ViewFeatures/src/DefaultHtmlGenerator.cs

ASP.NET Core 本身已经注册了 HtmlEncoder 的服务,并且提供了使用 WebEncoderOptions 的方式进行配置。

代码如下所示:

        public static IServiceCollection AddWebEncoders(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} services.AddOptions(); // Register the default encoders
// We want to call the 'Default' property getters lazily since they perform static caching
services.TryAddSingleton(
CreateFactory(() => HtmlEncoder.Default, settings => HtmlEncoder.Create(settings)));
services.TryAddSingleton(
CreateFactory(() => JavaScriptEncoder.Default, settings => JavaScriptEncoder.Create(settings)));
services.TryAddSingleton(
CreateFactory(() => UrlEncoder.Default, settings => UrlEncoder.Create(settings))); return services;
}
...
private static Func<IServiceProvider, TService> CreateFactory<TService>(
Func<TService> defaultFactory,
Func<TextEncoderSettings, TService> customSettingsFactory)
{
return serviceProvider =>
{
var settings = serviceProvider
?.GetService<IOptions<WebEncoderOptions>>()
?.Value
?.TextEncoderSettings;
return (settings != null) ? customSettingsFactory(settings) : defaultFactory();
};
}

这里使用了一个工厂来创建 HtmlEncoder 等 3 个对象。该工厂在创建过程中还会尝试寻找 WebEncoderOptions 配置对象,如果有的话,会使用它作为配置参数来创建这 3 个对象,否则使用默认方式。

另外还提供了一个可以注册 WebEncoderOptions 选项的扩展方法,提供使用 Options 模式的支持。通过它可以配置当前使用的编码范围。

public static IServiceCollection AddWebEncoders(this IServiceCollection services, Action<WebEncoderOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
} services.AddWebEncoders();
services.Configure(setupAction); return services;
}

源码见:https://github.com/dotnet/aspnetcore/blob/master/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs

所以,有两个方案可以选择,一个是直接从 HtmelEncoder 入手,提供新的 HtmlEncoder 实现,更好的方式是通过 Options 模式,在创建的时候,提供适当的参数。

有多种方式可以考虑,这里列出 5 种方式。

第一种方式是直接再注册一个同类型的服务,由于对同一个类型注册多个服务的话,在注入单个服务实例的时候,会采用最后注册的服务,所以,我们可以再注册一个 HtmlEncoder 类型的服务,就可以解决这个问题。

代码如下:

services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));

这种方式有个缺点,多创建一个对象实例。

第二种方式是直接替换掉原来的 HtmlEncoder 服务,可以使用 ServiceCollection 的扩展方法 Replace() 来实现,它接受一个 ServiceDescriptor 类型的参数进行替换。这样更彻底一点。

var descriptor =
new ServiceDescriptor(
typeof(HtmlEncoder),
HtmlEncoder.Create(UnicodeRanges.All));
services.Replace(descriptor);

这时候,只有一个扩展之后的 HtmlEncoder 在使用。此时没有多余的 HtmlEncoder 对象。

需要注意的是,Replace() 扩展方法位于命名空间 System.Text.Encodings.Web 下,而 UnicodeRanges 位于 System.Text.Unicode 下,记得添加两个命名空间的引用。

using System.Text.Encodings.Web;
using System.Text.Unicode;

第三种,既然提供了可以通过 Options 模式配置,还可以基于 Options 模式来处理。它使用 Configure 方法来进行,它通过 Action 来提供配置 WebEncoderOptions 对象。这样 3 种对象都可以直接使用,该方法定义如下:

public static IServiceCollection Configure<TOptions>(
this IServiceCollection services,
Action<TOptions> configureOptions) where TOptions : class;

实现如下:

services.Configure<Microsoft.Extensions.WebEncoders.WebEncoderOptions>(
options =>
options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));

这样在创建 HtmlEncoder 等 3 个对象的时候,将使用该配置对象。

第四种方式,还是基于 Options 模式。是在 Configure() 方法之后进行配置。见:

services.PostConfigure<Microsoft.Extensions.WebEncoders.WebEncoderOptions>(
options =>
options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));

效果与 #3 是相同的。但是 PostConfigure() 会保证在 Configure() 方法之后执行,比第 3 种更好。

最后但是最好的方式,根据源码可以看到,还可以使用第 5 种方式,系统已经提供了扩展方法 AddWebEncoders。其实与 #3 是一样的。

services.AddWebEncoders(options => options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));

这种方式更好一些。更加语义化。

依赖注入在 dotnet core 中实现与使用:5. 使用支持 Unicode 的 HtmlEncoder的更多相关文章

  1. 依赖注入在 dotnet core 中实现与使用:1 基本概念

    关于 Microsoft Extension: DependencyInjection 的介绍已经很多,但是多数偏重于实现原理和一些特定的实现场景.作为 dotnet core 的核心基石,这里准备全 ...

  2. 依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection

    既然是依赖注入容器,必然会涉及到服务的注册,获取服务实例,管理作用域,服务注入这四个方面. 服务注册涉及如何将我们的定义的服务注册到容器中.这通常是实际开发中使用容器的第一步,而容器本身通常是由框架来 ...

  3. 依赖注入在 dotnet core 中实现与使用:4. 集成 Autofac

    本示例使用 .net core 5 rc-1 实现. 1. 添加 Nuget 包引用 使用 Autofac 当然要添加 Autofac 的 Nuget 包,主要涉及到两个: Autofac.Exten ...

  4. 依赖注入在 dotnet core 中实现与使用:3 使用 Lazy<T> 延迟实例化

    有些对象我们并不想一开始就实例化,由于性能或者功能的考虑,希望等到使用的时候再实例化.考虑存在一个类 A, 它使用了依赖的类 B,在 A 中,只有某些不常用到的方法会涉及调用 B 中的方法,多数情况下 ...

  5. Dotnet Core中使用AutoMapper

    官网:http://automapper.org/ 文档:https://automapper.readthedocs.io/en/latest/index.html GitHub:https://g ...

  6. 依赖注入[8]: .NET Core DI框架[服务消费]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServicePr ...

  7. 依赖注入[7]: .NET Core DI框架[服务注册]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...

  8. 依赖注入[6]: .NET Core DI框架[编程体验]

    毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动时构建请求处理管道过程中,以及利用该管道处理每个请求过程中使用到的服务对象均来源于DI容器.该DI容器不仅为A ...

  9. .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]

    原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...

  10. .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]

    原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...

随机推荐

  1. duxui:基于Taro,兼容React Native、小程序、H5的多端UI库

    duxui是duxapp官方开发的一款兼容多端的UI组件库,兼容小程序.H5.React Native,库中提供了60+的组件,覆盖大部分使用场景 它能帮助你通过统一的组件样式,快速完成多端应用的开发 ...

  2. 【USB3.0协议学习】Topic1·USB3.0Hub的一些机制

    一.USB3.0 Hub的单播(非广播)机制 Hub通过解析下行packet header中的Route String字段识别packet要传递的终点,其中4'b0000代表hub本身,4'b0001 ...

  3. electon的入口文件 main 指定

    任何 Electron 应用程序的入口都是 main 文件. 这个文件控制了主进程,它运行在一个完整的Node.js环境中,负责控制您应用的生命周期,显示原生界面,执行特殊操作并管理渲染器进程(稍后详 ...

  4. 元素偏移量offset 与 style 区别 ?

    offset 可以得到任意样式表中的样式值offset 系列获得的数值是没有单位的数字offsetWidth 包含padding+border+width offsetWidth 等属性是只读属性,只 ...

  5. tekton初次安装报错“containers with incomplete status: [place-tools]”

    报错内容 在按照官方部署方式部署完毕以后,执行第一个taskrun的时候就报错了,报错如下 Status: Conditions: Last Transition Time: 2022-08-08T0 ...

  6. KubeSphere 社区双周报| 2024.08.16-08.29

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  7. Windows安全中心在手动删除威胁文件后无法处理,一直显示有威胁

    从网络上找到了一个简单靠谱的解决方案,与众位分享: 找到C:\ProgramData\Microsoft\Windows Defender\Scans\History\Service\Detectio ...

  8. API和SDK的区别

    API 和 SDK 有以下区别: 定义与功能: API(应用程序编程接口):是一组定义了软件组件之间交互规范的接口,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而无需访问源码或 ...

  9. MD5文件的计算

    Windows下的命令 certutil -hashfile <文件名> <hash类型> 如: certutil -hashfile "C:\1.txt" ...

  10. 锋利的在线诊断工具——Arthas

    导航 前言 火线告警,CPU飚了 服务重启,迅速救火 黑盒:无尽的猜测和不安 Arthas:锋利的Java诊断工具 在线追踪Cpu占比高的代码段 代码重构,星夜上线,稳了 结语 参考 肮脏的代码必须重 ...