Razor 集成
Razor模板是一个标准的C#类,因此你可以自由使用C#的功能,例如依赖注入、使用LINQ、自定义方法,甚至使用仓储。
安装
建议使用ABP CLI来安装此包。
使用 ABP CLI
在项目文件夹(.csproj 文件所在目录)中打开命令行窗口,输入以下命令:
abp add-package Volo.Abp.TextTemplating.Razor
手动安装
如果你想手动安装:
将Volo.Abp.TextTemplating.Razor NuGet包添加到你的项目:
dotnet add package Volo.Abp.TextTemplating.Razor将
AbpTextTemplatingRazorModule添加到模块的依赖列表中:[DependsOn( //...其他依赖 typeof(AbpTextTemplatingRazorModule) //添加新模块依赖 )] public class YourModule : AbpModule { }
向 CSharpCompilerOptions 添加 MetadataReference
你需要将模板中使用的类型的MetadataReference添加到CSharpCompilerOptions的References中。
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
{
options.References.Add(MetadataReference.CreateFromFile(typeof(YourModule).Assembly.Location));
});
}
为模板添加 MetadataReference
你可以为模板添加一些MetadataReference。
public override void ConfigureServices(ServiceConfigurationContext context)
{
services.Configure<AbpCompiledViewProviderOptions>(options =>
{
//"Hello"是模板名称。
options.TemplateReferences.Add("Hello", new List<Assembly>()
{
Assembly.Load("Microsoft.Extensions.Logging.Abstractions"),
Assembly.Load("Microsoft.Extensions.Logging")
}
.Select(x => MetadataReference.CreateFromFile(x.Location))
.ToList());
});
}
定义模板
在渲染模板之前,你应该先定义它。创建一个继承自TemplateDefinitionProvider基类的类:
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //模板名称:"Hello"
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/Hello/Hello.cshtml", //模板内容路径
isInlineLocalized: true
)
);
}
}
context对象用于添加新模板或获取依赖模块定义的模板。使用context.Add(...)来定义新模板。TemplateDefinition类代表一个模板。每个模板必须有一个唯一的名称(在渲染模板时会用到)。/Demos/Hello/Hello.cshtml是模板文件的路径。isInlineLocalized用于声明你是对所有语言使用单个模板(true)还是为每种语言使用不同的模板(false)。详见下文本地化部分。WithRenderEngine方法用于设置模板的渲染引擎。
模板基类
每个cshtml模板页都需要继承RazorTemplatePageBase或RazorTemplatePageBase<Model>。
基类中有一些有用的属性可以在模板中使用。例如:Localizer、ServiceProvider。
模板内容
WithVirtualFilePath表明我们使用虚拟文件系统来存储模板内容。在你的项目中创建一个Hello.cshtml文件,并在属性窗口中将其标记为“嵌入式资源”:
Hello.cshtml内容示例如下:
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<HelloModelNamespace.HelloModel>
Hello @Model.Name
HelloModel类如下:
namespace HelloModelNamespace
{
public class HelloModel
{
public string Name { get; set; }
}
}
虚拟文件系统要求在你的模块类的ConfigureServices方法中添加你的文件:
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
TextTemplateDemoModule是你在其中定义模板的模块类。TextTemplateDemo是你项目的根命名空间。
渲染模板
ITemplateRenderer服务用于渲染模板内容。
示例:渲染简单模板
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //模板名称
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
HelloDemo是一个简单的类,在其构造函数中注入ITemplateRenderer并在RunAsync方法中使用它。RenderAsync接收两个基本参数:templateName:要渲染的模板名称(本例中为Hello)。model:在模板内部用作model的对象(本例中为HelloModel对象)。
此示例的结果如下:
Hello John :)
本地化
可以根据当前文化对模板内容进行本地化。有两种本地化选项,在以下章节中描述。
内联本地化
内联本地化使用本地化系统来本地化模板内的文本。
示例:重置密码链接
假设你需要向用户发送电子邮件以重置他/她的密码。这里是模型/模板内容:
namespace ResetMyPasswordModelNamespace
{
public class ResetMyPasswordModel
{
public string Link { get; set; }
public string Name { get; set; }
}
}
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<ResetMyPasswordModelNamespace.ResetMyPasswordModel>
[@Localizer["ResetMyPassword", Model.Name]](/docs/zh-Hans/abp-docs/master/framework/infrastructure/text-templating/@Model.Link)
Localizer服务用于根据当前用户文化本地化给定的键。你需要在本地化文件中定义ResetMyPassword键:
"ResetMyPasswordTitle": "重置我的密码",
"ResetMyPassword": "你好 {0},点击这里重置你的密码"
你还需要在模板定义提供者类中声明与此模板一起使用的本地化资源:
context.Add(
new TemplateDefinition(
"PasswordReset", //模板名称
typeof(DemoResource) //本地化资源
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.cshtml", //模板内容路径
isInlineLocalized: true
)
);
这样就可以了。当你像这样渲染此模板时:
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //模板名称
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
你将看到本地化的结果:
<a title="重置我的密码" href="https://abp.io/example-link?userId=123&token=ABC">你好 john,点击这里重置你的密码</a>
如果你为应用程序定义了默认本地化资源,则无需为模板定义声明资源类型。
多内容本地化
与其使用本地化系统来本地化单个模板,你可能希望为每种语言创建不同的模板文件。如果模板对于特定文化需要完全不同(而不仅仅是简单的文本本地化),则可能需要这样做。
示例:欢迎邮件模板
假设你想向用户发送欢迎邮件,但希望根据用户文化定义完全不同的模板。
首先,创建一个文件夹并将你的模板放入其中,例如en.cshtml、tr.cshtml... 为你支持的每种文化创建一个:

然后在模板定义提供者类中添加你的模板定义:
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //模板内容文件夹
isInlineLocalized: false
)
);
- 设置默认文化名称,以便在没有所需文化的模板时回退到默认文化。
- 指定模板文件夹而不是单个模板文件。
- 对于这种情况,将
isInlineLocalized设置为false。
这样就可以了,你可以为当前文化渲染模板:
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
为保持示例简洁,此处省略了模型,但你可以像之前解释的那样使用模型。
指定文化
如果未指定,ITemplateRenderer服务使用当前文化(CultureInfo.CurrentUICulture)。如果需要,你可以将文化指定为cultureName参数:
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
布局模板
布局模板用于在其他模板之间创建共享布局。它类似于 ASP.NET Core MVC / Razor Pages 中的布局系统。
示例:电子邮件 HTML 布局模板
例如,你可能希望为所有电子邮件模板创建一个统一的布局。
首先,像之前一样创建一个模板文件:
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
@Body
</body>
</html>
- 布局模板必须有一个
Body部分作为渲染子内容的占位符。
然后在模板定义提供者中注册你的模板:
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //设置 isLayout!
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.cshtml",
isInlineLocalized: true
)
);
现在,你可以将此模板用作任何其他模板的布局:
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //设置 LAYOUT
)
.WithRazorEngine()
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
全局上下文
ABP传递model,该模型可以在模板内部访问。如果需要,你可以传递更多全局变量。
模板内容示例:
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
A global object value: @GlobalContext["myGlobalObject"]
此模板假设模板渲染上下文中存在一个myGlobalObject对象。你可以如下所示提供它:
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
渲染结果将是:
A global object value: TEST VALUE
替换现有模板
可以替换应用程序中使用的模块定义的模板。这样,你可以根据自己的需求自定义模板,而无需更改模块代码。
选项-1:使用虚拟文件系统
虚拟文件系统允许你通过将相同文件放置到项目中的相同路径来覆盖任何文件。
示例:替换标准电子邮件布局模板
ABP提供了一个电子邮件发送系统,该系统内部使用文本模板来渲染电子邮件内容。它在/Volo/Abp/Emailing/Templates/Layout.cshtml路径定义了一个标准的电子邮件布局模板。该模板的唯一名称是Abp.StandardEmailTemplates.Layout,此字符串定义为Volo.Abp.Emailing.Templates.StandardEmailTemplates静态类上的常量。
按照以下步骤用你自己的模板文件替换该模板:
1) 在你的项目中相同位置(/Volo/Abp/Emailing/Templates/Layout.cshtml)添加一个新文件:

2) 准备你的电子邮件布局模板:
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>这是我的页眉</h1>
@Body
<footer>
这是我的页脚...
</footer>
</body>
</html>
此示例简单地向模板添加了页眉和页脚,并在它们之间渲染内容(参见上面的布局模板部分以理解)。
3) 在.csproj文件中配置嵌入式资源
将Microsoft.Extensions.FileProviders.Embedded NuGet包添加到项目。
在
.csproj文件的<PropertyGroup>...</PropertyGroup>部分添加<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>。将以下代码添加到你的
.csproj文件:<ItemGroup> <None Remove="Volo\Abp\Emailing\Templates\*.cshtml" /> <EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.cshtml" /> </ItemGroup>这使模板文件成为“嵌入式资源”。
4) 配置虚拟文件系统
在你的模块的ConfigureServices方法中配置AbpVirtualFileSystemOptions,将嵌入式文件添加到虚拟文件系统:
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
在此示例代码中,BookStoreDomainModule应是你的模块名称。
请确保你的模块(直接或间接)依赖于
AbpEmailingModule。因为VFS可以根据依赖顺序覆盖文件。
现在,当你想要渲染电子邮件布局模板时,将使用你的模板。
选项-2:使用模板定义提供者
你可以创建一个模板定义提供者类,该类获取电子邮件布局模板并更改该模板的虚拟文件路径。
示例:使用/MyTemplates/EmailLayout.cshtml文件代替标准模板
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing.Templates;
using Volo.Abp.TextTemplating;
namespace MyProject
{
public class MyTemplateDefinitionProvider
: TemplateDefinitionProvider, ITransientDependency
{
public override void Define(ITemplateDefinitionContext context)
{
var emailLayoutTemplate = context.GetOrNull(StandardEmailTemplates.Layout);
emailLayoutTemplate
.WithVirtualFilePath(
"/MyTemplates/EmailLayout.cshtml",
isInlineLocalized: true
);
}
}
}
你仍然需要如前所述将文件/MyTemplates/EmailLayout.cshtml添加到虚拟文件系统中。这种方法允许你将模板定位在任何文件夹中,而不是依赖模块定义的文件夹。
除了模板内容,你还可以操作模板定义属性,如DisplayName、Layout或LocalizationSource。
高级特性
本节介绍文本模板系统的一些内部原理和更高级的用法。
模板内容提供者
ITemplateRenderer用于渲染模板,在大多数情况下这正是你想要的。但是,你可以使用ITemplateContentProvider来获取原始(未渲染的)模板内容。
ITemplateContentProvider在内部被ITemplateRenderer用来获取原始模板内容。
示例:
public class TemplateContentDemo : ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
public TemplateContentDemo(ITemplateContentProvider templateContentProvider)
{
_templateContentProvider = templateContentProvider;
}
public async Task RunAsync()
{
var result = await _templateContentProvider
.GetContentOrNullAsync("Hello");
Console.WriteLine(result);
}
}
结果将是原始模板内容:
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<HelloModelNamespace.HelloModel>
Hello @Model.Name
GetContentOrNullAsync如果请求的模板没有定义内容,则返回null。- 它可以接收一个
cultureName参数,如果模板针对不同文化有不同的文件,则会使用该参数(参见上文多内容本地化部分)。
模板内容贡献者
ITemplateContentProvider服务使用ITemplateContentContributor实现来查找模板内容。有一个预实现的内容贡献者VirtualFileTemplateContentContributor,它从虚拟文件系统中获取模板内容,如上所述。
你可以实现ITemplateContentContributor以从另一个源读取原始模板内容。
示例:
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: 尝试从另一个源查找内容
return null;
}
}
如果你的源找不到内容,则返回null,这样ITemplateContentProvider就会回退到下一个贡献者。
模板定义管理器
ITemplateDefinitionManager服务可用于获取模板定义(由模板定义提供者创建)。
抠丁客



