Browse Source

Merge pull request #8392 from abpframework/maliming/razor-template

Razor engine support for text templating
pull/8655/head
Halil İbrahim Kalkan 5 years ago
committed by GitHub
parent
commit
c5bf65527e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 569
      docs/en/Text-Templating-Razor.md
  2. 517
      docs/en/Text-Templating-Scriban.md
  3. 540
      docs/en/Text-Templating.md
  4. 12
      docs/en/docs-nav.json
  5. BIN
      docs/en/images/hello-template-razor.png
  6. BIN
      docs/en/images/multiple-file-template-razor.png
  7. BIN
      docs/en/images/replace-email-layout-razor.png
  8. 35
      framework/Volo.Abp.sln
  9. 3
      framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xml
  10. 30
      framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xsd
  11. 16
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo.Abp.TextTemplating.Abstractions.csproj
  12. 46
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingAbstractionsModule.cs
  13. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs
  14. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentContributor.cs
  15. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentProvider.cs
  16. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs
  17. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs
  18. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs
  19. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateRenderer.cs
  20. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs
  21. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentProvider.cs
  22. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinition.cs
  23. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs
  24. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs
  25. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs
  26. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs
  27. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs
  28. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs
  29. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs
  30. 3
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs
  31. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs
  32. 0
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs
  33. 8
      framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs
  34. 3
      framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xml
  35. 30
      framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xsd
  36. 21
      framework/src/Volo.Abp.TextTemplating.Razor/Volo.Abp.TextTemplating.Razor.csproj
  37. 15
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions.cs
  38. 107
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompiler.cs
  39. 15
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompilerOptions.cs
  40. 9
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateConsts.cs
  41. 12
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpTextTemplatingRazorModule.cs
  42. 77
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs
  43. 20
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpRazorProjectEngineFactory.cs
  44. 30
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/EmptyProjectFileSystem.cs
  45. 10
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpCompiledViewProvider.cs
  46. 11
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpRazorProjectEngineFactory.cs
  47. 34
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IRazorTemplatePage.cs
  48. 28
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/NotFoundProjectItem.cs
  49. 177
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplatePageBase.cs
  50. 168
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer.cs
  51. 3
      framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xml
  52. 30
      framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xsd
  53. 19
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo.Abp.TextTemplating.Scriban.csproj
  54. 12
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/AbpTextTemplatingScribanModule.cs
  55. 6
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateLocalizer.cs
  56. 12
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer.cs
  57. 13
      framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj
  58. 41
      framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs
  59. 23
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo.Abp.TextTemplating.Razor.Tests.csproj
  60. 48
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs
  61. 26
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorLocalizedTemplateContentReaderFactory_Tests.cs
  62. 7
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateDefinitionTests.cs
  63. 145
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer_Tests.cs
  64. 24
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs
  65. 26
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTextTemplatingTestModule.cs
  66. 23
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorVirtualFileTemplateContributor_Tests.cs
  67. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ForgotPasswordEmail.cshtml
  68. 7
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs
  69. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ShowDecimalNumber.cshtml
  70. 6
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplate.cshtml
  71. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplateLayout1.cshtml
  72. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/en.cshtml
  73. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/tr.cshtml
  74. 23
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo.Abp.TextTemplating.Scriban.Tests.csproj
  75. 0
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ForgotPasswordEmail.tpl
  76. 0
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ShowDecimalNumber.tpl
  77. 0
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/TestTemplateLayout1.tpl
  78. 0
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/en.tpl
  79. 0
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/tr.tpl
  80. 20
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanLocalizedTemplateContentReaderFactory_Tests.cs
  81. 7
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateDefinitionTests.cs
  82. 8
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer_Tests.cs
  83. 20
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs
  84. 20
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTextTemplatingTestModule.cs
  85. 14
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanVirtualFileTemplateContributor_Tests.cs
  86. 5
      framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj
  87. 4
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs
  88. 6
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs
  89. 2
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs
  90. 20
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs
  91. 10
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs
  92. 29
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs
  93. 35
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs
  94. 3
      nupkg/common.ps1

569
docs/en/Text-Templating-Razor.md

@ -0,0 +1,569 @@
# Razor Integration
The Razor template is a standard C# class, so you can freely use the functions of C#, such as `dependency injection`, using `LINQ`, custom methods, and even using `Repository`.
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.TextTemplating.Razor
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.TextTemplating.Razor](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Razor) NuGet package to your project:
````
Install-Package Volo.Abp.TextTemplating.Razor
````
2. Add the `AbpTextTemplatingRazorModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingRazorModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## Add MetadataReference to CSharpCompilerOptions
You need to add the `MetadataReference` of the type used in the template to `CSharpCompilerOptions's References`.
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
{
options.References.Add(MetadataReference.CreateFromFile(typeof(YourModule).Assembly.Location));
});
}
````
## Add MetadataReference for a template.
You can add some `MetadataReference` to the template
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
services.Configure<AbpCompiledViewProviderOptions>(options =>
{
//Hello is template name.
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());
});
}
````
## Defining Templates
Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.cshtml", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template.
* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template).
* `/Demos/Hello/Hello.cshtml` is the path of the template file.
* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more.
### The Template Base
Every `cshtml` template page needs to inherit `RazorTemplatePageBase` or `RazorTemplatePageBase<Model>`.
There are some useful properties in the base class that can be used in templates. eg: `Localizer`, `ServiceProvider`.
### The Template Content
`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.cshtml` file inside your project and mark it as "**embedded resource**" on the properties window:
![hello-template-razor](images/hello-template-razor.png)
Example `Hello.cshtml` content is shown below:
````
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<HelloModelNamespace.HelloModel>
Hello @Model.Name
````
The `HelloModel` class is:
````csharp
namespace HelloModelNamespace
{
public class HelloModel
{
public string Name { get; set; }
}
}
````
The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule` is the module class that you define your template in.
* `TextTemplateDemo` is the root namespace of your project.
## Rendering the Template
`ITemplateRenderer` service is used to render a template content.
### Example: Rendering a Simple Template
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method.
* `RenderAsync` gets two fundamental parameters:
* `templateName`: The name of the template to be rendered (`Hello` in this example).
* `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example).
The result shown below for this example:
````csharp
Hello John :)
````
## Localization
It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections.
### Inline localization
Inline localization uses the [localization system](Localization.md) to localize texts inside templates.
#### Example: Reset Password Link
Assuming you need to send an email to a user to reset her/his password. Here, the model/template content:
````csharp
namespace ResetMyPasswordModelNamespace
{
public class ResetMyPasswordModel
{
public string Link { get; set; }
public string Name { get; set; }
}
}
````
````html
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<ResetMyPasswordModelNamespace.ResetMyPasswordModel>
<a title="@Localizer["ResetMyPasswordTitle"]" href="@Model.Link">@Localizer["ResetMyPassword", Model.Name]</a>
````
`Localizer` service is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
You also need to declare the localization resource to be used with this template, inside your template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
).WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.cshtml", //template content path
isInlineLocalized: true
)
);
````
That's all. When you render this template like that:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
You will see the localized result:
````html
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition.
### Multiple Contents Localization
Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations.
#### Example: Welcome Email Template
Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture.
First, create a folder and put your templates inside it, like `en.cshtml`, `tr.cshtml`... one for each culture you support:
![multiple-file-template-razor](images/multiple-file-template-razor.png)
Then add your template definition in the template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture.
* Specify **the template folder** rather than a single template file.
* Set `isInlineLocalized` to `false` for this case.
That's all, you can render the template for the current culture:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> Skipped the modal for this example to keep it simple, but you can use models as just explained before.
### Specify the Culture
`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter:
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## Layout Templates
Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages.
### Example: Email HTML Layout Template
For example, you may want to create a single layout for all of your email templates.
First, create a template file just like before:
````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>
````
* A layout template must have a `Body` part as a place holder for the rendered child content.
The register your template in the template definition provider:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
).WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.cshtml",
isInlineLocalized: true
)
);
````
Now, you can use this template as the layout of any other template:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
).WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## Global Context
ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need.
An example template content:
````html
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
A global object value: @GlobalContext["myGlobalObject"]
````
This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
The rendering result will be:
````
A global object value: TEST VALUE
````
## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
### Option-1: Using the Virtual File System
The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project.
#### Example: Replace the Standard Email Layout Template
ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.cshtml` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class.
Do the following steps to replace the template file with your own;
**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.cshtml`) in your project:
![replace-email-layout-razor](images/replace-email-layout-razor.png)
**2)** Prepare your email layout template:
````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>
<h1>This my header</h1>
@Body
<footer>
This is my footer...
</footer>
</body>
</html>
````
This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it).
**3)** Configure the embedded resources in the `.csproj` file
* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project.
* Add `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` into the `<PropertyGroup>...</PropertyGroup>` section of your `.csproj` file.
* Add the following code into your `.csproj` file:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.cshtml" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.cshtml" />
</ItemGroup>
````
This makes the template files "embedded resource".
**4)** Configure the virtual file system
Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` should be your module name, in this example code.
> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order.
Now, your template will be used when you want to render the email layout template.
### Option-2: Using the Template Definition Provider
You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template.
**Example: Use the `/MyTemplates/EmailLayout.cshtml` file instead of the standard template**
```csharp
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
);
}
}
}
```
You should still add the file `/MyTemplates/EmailLayout.cshtml` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module.
Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`.
## Advanced Features
This section covers some internals and more advanced usages of the text templating system.
### Template Content Provider
`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents.
> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents.
Example:
````csharp
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);
}
}
````
The result will be the raw template content:
````
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<HelloModelNamespace.HelloModel>
Hello @Model.Name
````
* `GetContentOrNullAsync` returns `null` if no content defined for the requested template.
* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above).
### Template Content Contributor
`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above.
You can implement the `ITemplateContentContributor` to read raw template contents from another source.
Example:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor.
### Template Definition Manager
`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers).
## See Also
* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
* [Localization system](Localization.md).
* [Virtual File System](Virtual-File-System.md).

517
docs/en/Text-Templating-Scriban.md

@ -0,0 +1,517 @@
# Razor Integration
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.TextTemplating.Scriban
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.TextTemplating.Scriban](https://www.nuget.org/packages/Volo.Abp.TextTemplating.Scriban) NuGet package to your project:
````
Install-Package Volo.Abp.TextTemplating.Scriban
````
2. Add the `AbpTextTemplatingScribanModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingScribanModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## Defining Templates
Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.tpl", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template.
* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template).
* `/Demos/Hello/Hello.tpl` is the path of the template file.
* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more.
### The Template Content
`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.tpl` file inside your project and mark it as "**embedded resource**" on the properties window:
![hello-template](images/hello-template.png)
Example `Hello.tpl` content is shown below:
````
Hello {%{{{model.name}}}%} :)
````
The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule` is the module class that you define your template in.
* `TextTemplateDemo` is the root namespace of your project.
## Rendering the Template
`ITemplateRenderer` service is used to render a template content.
### Example: Rendering a Simple Template
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method.
* `RenderAsync` gets two fundamental parameters:
* `templateName`: The name of the template to be rendered (`Hello` in this example).
* `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example).
The result shown below for this example:
````csharp
Hello John :)
````
### Anonymous Model
While it is suggested to create model classes for the templates, it would be practical (and possible) to use anonymous objects for simple cases:
````csharp
var result = await _templateRenderer.RenderAsync(
"Hello",
new
{
Name = "John"
}
);
````
In this case, we haven't created a model class, but created an anonymous object as the model.
### PascalCase vs snake_case
PascalCase property names (like `UserName`) is used as snake_case (like `user_name`) in the templates.
## Localization
It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections.
### Inline localization
Inline localization uses the [localization system](Localization.md) to localize texts inside templates.
#### Example: Reset Password Link
Assuming you need to send an email to a user to reset her/his password. Here, the template content:
````
<a title="{%{{{L "ResetMyPasswordTitle"}}}%}" href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword" model.name}}}%}</a>
````
`L` function is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
You also need to declare the localization resource to be used with this template, inside your template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
).WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.tpl", //template content path
isInlineLocalized: true
)
);
````
That's all. When you render this template like that:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
You will see the localized result:
````csharp
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition.
### Multiple Contents Localization
Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations.
#### Example: Welcome Email Template
Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture.
First, create a folder and put your templates inside it, like `en.tpl`, `tr.tpl`... one for each culture you support:
![multiple-file-template](images/multiple-file-template.png)
Then add your template definition in the template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture.
* Specify **the template folder** rather than a single template file.
* Set `isInlineLocalized` to `false` for this case.
That's all, you can render the template for the current culture:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> Skipped the modal for this example to keep it simple, but you can use models as just explained before.
### Specify the Culture
`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter:
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## Layout Templates
Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages.
### Example: Email HTML Layout Template
For example, you may want to create a single layout for all of your email templates.
First, create a template file just like before:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
* A layout template must have a **{%{{{content}}}%}** part as a place holder for the rendered child content.
The register your template in the template definition provider:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
).WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.tpl",
isInlineLocalized: true
)
);
````
Now, you can use this template as the layout of any other template:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
).WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## Global Context
ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need.
An example template content:
````
A global object value: {%{{{myGlobalObject}}}%}
````
This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
The rendering result will be:
````
A global object value: TEST VALUE
````
## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
### Option-1: Using the Virtual File System
The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project.
#### Example: Replace the Standard Email Layout Template
ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.tpl` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class.
Do the following steps to replace the template file with your own;
**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.tpl`) in your project:
![replace-email-layout](images/replace-email-layout.png)
**2)** Prepare your email layout template:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>This my header</h1>
{%{{{content}}}%}
<footer>
This is my footer...
</footer>
</body>
</html>
````
This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it).
**3)** Configure the embedded resources in the `.csproj` file
* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project.
* Add `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` into the `<PropertyGroup>...</PropertyGroup>` section of your `.csproj` file.
* Add the following code into your `.csproj` file:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.tpl" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.tpl" />
</ItemGroup>
````
This makes the template files "embedded resource".
**4)** Configure the virtual file system
Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` should be your module name, in this example code.
> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order.
Now, your template will be used when you want to render the email layout template.
### Option-2: Using the Template Definition Provider
You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template.
**Example: Use the `/MyTemplates/EmailLayout.tpl` file instead of the standard template**
```csharp
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.tpl",
isInlineLocalized: true
);
}
}
}
```
You should still add the file `/MyTemplates/EmailLayout.tpl` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module.
Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`.
## Advanced Features
This section covers some internals and more advanced usages of the text templating system.
### Template Content Provider
`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents.
> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents.
Example:
````csharp
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);
}
}
````
The result will be the raw template content:
````
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` returns `null` if no content defined for the requested template.
* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above).
### Template Content Contributor
`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above.
You can implement the `ITemplateContentContributor` to read raw template contents from another source.
Example:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor.
### Template Definition Manager
`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers).
## See Also
* [The source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
* [Localization system](Localization.md).
* [Virtual File System](Virtual-File-System.md).

540
docs/en/Text-Templating.md

@ -12,549 +12,21 @@ It is very similar to an ASP.NET Core Razor View (or Page):
You can use the rendered output for any purpose, like sending emails or preparing some reports.
### Example
Here, a simple template:
````
Hello {%{{{model.name}}}%} :)
````
You can define a class with a `Name` property to render this template:
````csharp
public class HelloModel
{
public string Name { get; set; }
}
````
If you render the template with a `HelloModel` with the `Name` is `John`, the rendered output is will be:
````
Hello John :)
````
Template rendering engine is very powerful;
* It is based on the [Scriban](https://github.com/lunet-io/scriban) library, so it supports **conditional logics**, **loops** and much more.
* It supports **conditional logics**, **loops** and much more.
* Template content **can be localized**.
* You can define **layout templates** to be used as the layout while rendering other templates.
* You can pass arbitrary objects to the template context (beside the model) for advanced scenarios.
### Source Code
Get [the source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
## Installation
It is suggested to use the [ABP CLI](CLI.md) to install this package.
### Using the ABP CLI
Open a command line window in the folder of the project (.csproj file) and type the following command:
````bash
abp add-package Volo.Abp.TextTemplating
````
### Manual Installation
If you want to manually install;
1. Add the [Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) NuGet package to your project:
````
Install-Package Volo.Abp.TextTemplating
````
2. Add the `AbpTextTemplatingModule` to the dependency list of your module:
````csharp
[DependsOn(
//...other dependencies
typeof(AbpTextTemplatingModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
}
````
## Defining Templates
Before rendering a template, you should define it. Create a class inheriting from the `TemplateDefinitionProvider` base class:
````csharp
public class DemoTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition("Hello") //template name: "Hello"
.WithVirtualFilePath(
"/Demos/Hello/Hello.tpl", //template content path
isInlineLocalized: true
)
);
}
}
````
* `context` object is used to add new templates or get the templates defined by depended modules. Used `context.Add(...)` to define a new template.
* `TemplateDefinition` is the class represents a template. Each template must have a unique name (that will be used while you are rendering the template).
* `/Demos/Hello/Hello.tpl` is the path of the template file.
* `isInlineLocalized` is used to declare if you are using a single template for all languages (`true`) or different templates for each language (`false`). See the Localization section below for more.
### The Template Content
`WithVirtualFilePath` indicates that we are using the [Virtual File System](Virtual-File-System.md) to store the template content. Create a `Hello.tpl` file inside your project and mark it as "**embedded resource**" on the properties window:
![hello-template](images/hello-template.png)
Example `Hello.tpl` content is shown below:
````
Hello {%{{{model.name}}}%} :)
````
The [Virtual File System](Virtual-File-System.md) requires to add your files in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class:
````csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<TextTemplateDemoModule>("TextTemplateDemo");
});
````
* `TextTemplateDemoModule` is the module class that you define your template in.
* `TextTemplateDemo` is the root namespace of your project.
## Rendering the Template
`ITemplateRenderer` service is used to render a template content.
### Example: Rendering a Simple Template
````csharp
public class HelloDemo : ITransientDependency
{
private readonly ITemplateRenderer _templateRenderer;
public HelloDemo(ITemplateRenderer templateRenderer)
{
_templateRenderer = templateRenderer;
}
public async Task RunAsync()
{
var result = await _templateRenderer.RenderAsync(
"Hello", //the template name
new HelloModel
{
Name = "John"
}
);
Console.WriteLine(result);
}
}
````
* `HelloDemo` is a simple class that injects the `ITemplateRenderer` in its constructor and uses it inside the `RunAsync` method.
* `RenderAsync` gets two fundamental parameters:
* `templateName`: The name of the template to be rendered (`Hello` in this example).
* `model`: An object that is used as the `model` inside the template (a `HelloModel` object in this example).
The result shown below for this example:
````csharp
Hello John :)
````
### Anonymous Model
While it is suggested to create model classes for the templates, it would be practical (and possible) to use anonymous objects for simple cases:
````csharp
var result = await _templateRenderer.RenderAsync(
"Hello",
new
{
Name = "John"
}
);
````
In this case, we haven't created a model class, but created an anonymous object as the model.
### PascalCase vs snake_case
PascalCase property names (like `UserName`) is used as snake_case (like `user_name`) in the templates.
## Localization
It is possible to localize a template content based on the current culture. There are two types of localization options described in the following sections.
### Inline localization
Inline localization uses the [localization system](Localization.md) to localize texts inside templates.
#### Example: Reset Password Link
Assuming you need to send an email to a user to reset her/his password. Here, the template content:
````
<a title="{%{{{L "ResetMyPasswordTitle"}}}%}" href="{%{{{model.link}}}%}">{%{{{L "ResetMyPassword" model.name}}}%}</a>
````
`L` function is used to localize the given key based on the current user culture. You need to define the `ResetMyPassword` key inside your localization file:
````json
"ResetMyPasswordTitle": "Reset my password",
"ResetMyPassword": "Hi {0}, Click here to reset your password"
````
You also need to declare the localization resource to be used with this template, inside your template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
"PasswordReset", //Template name
typeof(DemoResource) //LOCALIZATION RESOURCE
).WithVirtualFilePath(
"/Demos/PasswordReset/PasswordReset.tpl", //template content path
isInlineLocalized: true
)
);
````
That's all. When you render this template like that:
````csharp
var result = await _templateRenderer.RenderAsync(
"PasswordReset", //the template name
new PasswordResetModel
{
Name = "john",
Link = "https://abp.io/example-link?userId=123&token=ABC"
}
);
````
You will see the localized result:
````csharp
<a title="Reset my password" href="https://abp.io/example-link?userId=123&token=ABC">Hi john, Click here to reset your password</a>
````
> If you define the [default localization resource](Localization.md) for your application, then no need to declare the resource type for the template definition.
### Multiple Contents Localization
Instead of a single template that uses the localization system to localize the template, you may want to create different template files for each language. It can be needed if the template should be completely different for a specific culture rather than simple text localizations.
#### Example: Welcome Email Template
Assuming that you want to send a welcome email to your users, but want to define a completely different template based on the user culture.
First, create a folder and put your templates inside it, like `en.tpl`, `tr.tpl`... one for each culture you support:
![multiple-file-template](images/multiple-file-template.png)
Then add your template definition in the template definition provider class:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en"
)
.WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates", //template content folder
isInlineLocalized: false
)
);
````
* Set **default culture name**, so it fallbacks to the default culture if there is no template for the desired culture.
* Specify **the template folder** rather than a single template file.
* Set `isInlineLocalized` to `false` for this case.
That's all, you can render the template for the current culture:
````csharp
var result = await _templateRenderer.RenderAsync("WelcomeEmail");
````
> Skipped the modal for this example to keep it simple, but you can use models as just explained before.
ABP Framework provides two types of engines;
### Specify the Culture
* **[Razor](Text-Templating-Razor.md)**
* **[Scriban](Text-Templating-Scriban.md)**
`ITemplateRenderer` service uses the current culture (`CultureInfo.CurrentUICulture`) if not specified. If you need, you can specify the culture as the `cultureName` parameter:
## Source Code
````csharp
var result = await _templateRenderer.RenderAsync(
"WelcomeEmail",
cultureName: "en"
);
````
## Layout Templates
Layout templates are used to create shared layouts among other templates. It is similar to the layout system in the ASP.NET Core MVC / Razor Pages.
### Example: Email HTML Layout Template
For example, you may want to create a single layout for all of your email templates.
First, create a template file just like before:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{%{{{content}}}%}
</body>
</html>
````
* A layout template must have a **{%{{{content}}}%}** part as a place holder for the rendered child content.
The register your template in the template definition provider:
````csharp
context.Add(
new TemplateDefinition(
"EmailLayout",
isLayout: true //SET isLayout!
).WithVirtualFilePath(
"/Demos/EmailLayout/EmailLayout.tpl",
isInlineLocalized: true
)
);
````
Now, you can use this template as the layout of any other template:
````csharp
context.Add(
new TemplateDefinition(
name: "WelcomeEmail",
defaultCultureName: "en",
layout: "EmailLayout" //Set the LAYOUT
).WithVirtualFilePath(
"/Demos/WelcomeEmail/Templates",
isInlineLocalized: false
)
);
````
## Global Context
ABP passes the `model` that can be used to access to the model inside the template. You can pass more global variables if you need.
An example template content:
````
A global object value: {%{{{myGlobalObject}}}%}
````
This template assumes that that is a `myGlobalObject` object in the template rendering context. You can provide it like shown below:
````csharp
var result = await _templateRenderer.RenderAsync(
"GlobalContextUsage",
globalContext: new Dictionary<string, object>
{
{"myGlobalObject", "TEST VALUE"}
}
);
````
The rendering result will be:
````
A global object value: TEST VALUE
````
## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
### Option-1: Using the Virtual File System
The [Virtual File System](Virtual-File-System.md) allows you to override any file by placing the same file into the same path in your project.
#### Example: Replace the Standard Email Layout Template
ABP Framework provides an [email sending system](Emailing.md) that internally uses the text templating to render the email content. It defines a standard email layout template in the `/Volo/Abp/Emailing/Templates/Layout.tpl` path. The unique name of the template is `Abp.StandardEmailTemplates.Layout` and this string is defined as a constant on the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` static class.
Do the following steps to replace the template file with your own;
**1)** Add a new file into the same location (`/Volo/Abp/Emailing/Templates/Layout.tpl`) in your project:
![replace-email-layout](images/replace-email-layout.png)
**2)** Prepare your email layout template:
````html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>This my header</h1>
{%{{{content}}}%}
<footer>
This is my footer...
</footer>
</body>
</html>
````
This example simply adds a header and footer to the template and renders the content between them (see the *Layout Templates* section above to understand it).
**3)** Configure the embedded resources in the `.csproj` file
* Add [Microsoft.Extensions.FileProviders.Embedded](https://www.nuget.org/packages/Microsoft.Extensions.FileProviders.Embedded) NuGet package to the project.
* Add `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` into the `<PropertyGroup>...</PropertyGroup>` section of your `.csproj` file.
* Add the following code into your `.csproj` file:
````xml
<ItemGroup>
<None Remove="Volo\Abp\Emailing\Templates\*.tpl" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.tpl" />
</ItemGroup>
````
This makes the template files "embedded resource".
**4)** Configure the virtual file system
Configure the `AbpVirtualFileSystemOptions` in the `ConfigureServices` method of your [module](Module-Development-Basics.md) to add the embedded files into the virtual file system:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BookStoreDomainModule>();
});
```
`BookStoreDomainModule` should be your module name, in this example code.
> Be sure that your module (directly or indirectly) [depends on](Module-Development-Basics.md) the `AbpEmailingModule`. Because the VFS can override files based on the dependency order.
Now, your template will be used when you want to render the email layout template.
### Option-2: Using the Template Definition Provider
You can create a template definition provider class that gets the email layout template and changes the virtual file path for the template.
**Example: Use the `/MyTemplates/EmailLayout.tpl` file instead of the standard template**
```csharp
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.tpl",
isInlineLocalized: true
);
}
}
}
```
You should still add the file `/MyTemplates/EmailLayout.tpl` to the virtual file system as explained before. This approach allows you to locate templates in any folder instead of the folder defined by the depended module.
Beside the template content, you can manipulate the template definition properties, like `DisplayName`, `Layout` or `LocalizationSource`.
## Advanced Features
This section covers some internals and more advanced usages of the text templating system.
### Template Content Provider
`ITemplateRenderer` is used to render the template, which is what you want for most of the cases. However, you can use the `ITemplateContentProvider` to get the raw (not rendered) template contents.
> `ITemplateContentProvider` is internally used by the `ITemplateRenderer` to get the raw template contents.
Example:
````csharp
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);
}
}
````
The result will be the raw template content:
````
Hello {%{{{model.name}}}%} :)
````
* `GetContentOrNullAsync` returns `null` if no content defined for the requested template.
* It can get a `cultureName` parameter that is used if template has different files for different cultures (see Multiple Contents Localization section above).
### Template Content Contributor
`ITemplateContentProvider` service uses `ITemplateContentContributor` implementations to find template contents. There is a single pre-implemented content contributor, `VirtualFileTemplateContentContributor`, which gets template contents from the virtual file system as described above.
You can implement the `ITemplateContentContributor` to read raw template contents from another source.
Example:
````csharp
public class MyTemplateContentProvider
: ITemplateContentContributor, ITransientDependency
{
public async Task<string> GetOrNullAsync(TemplateContentContributorContext context)
{
var templateName = context.TemplateDefinition.Name;
//TODO: Try to find content from another source
return null;
}
}
````
Return `null` if your source can not find the content, so `ITemplateContentProvider` fallbacks to the next contributor.
### Template Definition Manager
`ITemplateDefinitionManager` service can be used to get the template definitions (created by the template definition providers).
Get [the source code of the sample application](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) developed and referred through this document.
## See Also

12
docs/en/docs-nav.json

@ -334,7 +334,17 @@
},
{
"text": "Text Templating",
"path": "Text-Templating.md"
"path": "Text-Templating.md",
"items": [
{
"text": "Razor Integration",
"path": "Text-Templating-Razor.md"
},
{
"text": "Scriban Integration",
"path": "Text-Templating-Scriban.md"
}
]
},
{
"text": "Timing",

BIN
docs/en/images/hello-template-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/en/images/multiple-file-template-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/en/images/replace-email-layout-razor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

35
framework/Volo.Abp.sln

@ -383,6 +383,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Compone
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions", "src\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions.csproj", "{E9CE58DB-0789-4D18-8B63-474F7D7B14B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Abstractions", "src\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj", "{184E859A-282D-44D7-B8E9-FEA874644013}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Scriban", "src\Volo.Abp.TextTemplating.Scriban\Volo.Abp.TextTemplating.Scriban.csproj", "{228723E6-FA6D-406B-B8F8-C9BCC547AF8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Razor", "src\Volo.Abp.TextTemplating.Razor\Volo.Abp.TextTemplating.Razor.csproj", "{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Razor.Tests", "test\Volo.Abp.TextTemplating.Razor.Tests\Volo.Abp.TextTemplating.Razor.Tests.csproj", "{C996F458-98FB-483D-9306-4701290E2FC1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Scriban.Tests", "test\Volo.Abp.TextTemplating.Scriban.Tests\Volo.Abp.TextTemplating.Scriban.Tests.csproj", "{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1141,6 +1151,26 @@ Global
{E9CE58DB-0789-4D18-8B63-474F7D7B14B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9CE58DB-0789-4D18-8B63-474F7D7B14B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9CE58DB-0789-4D18-8B63-474F7D7B14B4}.Release|Any CPU.Build.0 = Release|Any CPU
{184E859A-282D-44D7-B8E9-FEA874644013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{184E859A-282D-44D7-B8E9-FEA874644013}.Debug|Any CPU.Build.0 = Debug|Any CPU
{184E859A-282D-44D7-B8E9-FEA874644013}.Release|Any CPU.ActiveCfg = Release|Any CPU
{184E859A-282D-44D7-B8E9-FEA874644013}.Release|Any CPU.Build.0 = Release|Any CPU
{228723E6-FA6D-406B-B8F8-C9BCC547AF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{228723E6-FA6D-406B-B8F8-C9BCC547AF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{228723E6-FA6D-406B-B8F8-C9BCC547AF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{228723E6-FA6D-406B-B8F8-C9BCC547AF8E}.Release|Any CPU.Build.0 = Release|Any CPU
{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19}.Release|Any CPU.Build.0 = Release|Any CPU
{C996F458-98FB-483D-9306-4701290E2FC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C996F458-98FB-483D-9306-4701290E2FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C996F458-98FB-483D-9306-4701290E2FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C996F458-98FB-483D-9306-4701290E2FC1}.Release|Any CPU.Build.0 = Release|Any CPU
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1334,6 +1364,11 @@ Global
{863C18F9-2407-49F9-9ADC-F6229AF3B385} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{B4B6B7DE-9798-4007-B1DF-7EE7929E392A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{E9CE58DB-0789-4D18-8B63-474F7D7B14B4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{184E859A-282D-44D7-B8E9-FEA874644013} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{228723E6-FA6D-406B-B8F8-C9BCC547AF8E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{42EA6F06-2D78-4D18-8AC4-8F2AB7E6DA19} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{C996F458-98FB-483D-9306-4701290E2FC1} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

3
framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

16
framework/src/Volo.Abp.TextTemplating.Abstractions/Volo.Abp.TextTemplating.Abstractions.csproj

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>
</Project>

46
framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingAbstractionsModule.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.TextTemplating
{
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpLocalizationAbstractionsModule)
)]
public class AbpTextTemplatingAbstractionsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddProvidersAndContributors(context.Services);
}
private static void AutoAddProvidersAndContributors(IServiceCollection services)
{
var definitionProviders = new List<Type>();
var contentContributors = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
definitionProviders.Add(context.ImplementationType);
}
if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType))
{
contentContributors.Add(context.ImplementationType);
}
});
services.Configure<AbpTextTemplatingOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(definitionProviders);
options.ContentContributors.AddIfNotContains(contentContributors);
});
}
}
}

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentContributor.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentProvider.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateRenderer.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentProvider.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinition.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs

3
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs

@ -64,7 +64,8 @@ namespace Volo.Abp.TextTemplating.VirtualFiles
if (fileInfo.IsDirectory)
{
var folderReader = new VirtualFolderLocalizedTemplateContentReader();
//TODO: Configure file extensions.
var folderReader = new VirtualFolderLocalizedTemplateContentReader(new[] {".tpl", ".cshtml"});
await folderReader.ReadContentsAsync(VirtualFileProvider, virtualPath);
return folderReader;
}

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs

0
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs

8
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs → framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs

@ -9,6 +9,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles
public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader
{
private Dictionary<string, string> _dictionary;
private readonly string[] _fileExtension;
public VirtualFolderLocalizedTemplateContentReader(string[] fileExtension)
{
_fileExtension = fileExtension;
}
public async Task ReadContentsAsync(
IVirtualFileProvider virtualFileProvider,
@ -29,7 +35,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles
continue;
}
_dictionary.Add(file.Name.RemovePostFix(".tpl"), await file.ReadAsStringAsync());
_dictionary.Add(file.Name.RemovePostFix(_fileExtension), await file.ReadAsStringAsync());
}
}

3
framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

21
framework/src/Volo.Abp.TextTemplating.Razor/Volo.Abp.TextTemplating.Razor.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftPackageVersion)" />
</ItemGroup>
</Project>

15
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Volo.Abp.TextTemplating.Razor
{
public class AbpCompiledViewProviderOptions
{
public Dictionary<string, List<PortableExecutableReference>> TemplateReferences { get; }
public AbpCompiledViewProviderOptions()
{
TemplateReferences = new Dictionary<string, List<PortableExecutableReference>>();
}
}
}

107
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompiler.cs

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating.Razor
{
public class AbpRazorTemplateCSharpCompiler : ISingletonDependency
{
protected AbpRazorTemplateCSharpCompilerOptions Options { get; }
public AbpRazorTemplateCSharpCompiler(IOptions<AbpRazorTemplateCSharpCompilerOptions> options)
{
Options = options.Value;
}
private static IEnumerable<PortableExecutableReference> DefaultReferences => new List<Assembly>()
{
Assembly.Load("netstandard"),
Assembly.Load("System.Private.CoreLib"),
Assembly.Load("System.Runtime"),
Assembly.Load("System.Collections"),
Assembly.Load("System.ComponentModel"),
Assembly.Load("System.Linq"),
Assembly.Load("System.Linq.Expressions"),
Assembly.Load("Microsoft.Extensions.DependencyInjection"),
Assembly.Load("Microsoft.Extensions.DependencyInjection.Abstractions"),
Assembly.Load("Microsoft.Extensions.Localization"),
Assembly.Load("Microsoft.Extensions.Localization.Abstractions"),
typeof(AbpRazorTemplateCSharpCompiler).Assembly
}
.Select(x => MetadataReference.CreateFromFile(x.Location))
.ToImmutableList();
public virtual Stream CreateAssembly(string code, string assemblyName, List<MetadataReference> references = null, CSharpCompilationOptions options = null)
{
var defaultReferences = DefaultReferences.Concat(Options.References);
try
{
var compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { CreateSyntaxTree(code) },
references: references != null ? defaultReferences.Concat(references) : defaultReferences,
options ?? GetCompilationOptions());
using (var memoryStream = new MemoryStream())
{
var result = compilation.Emit(memoryStream);
if (!result.Success)
{
var error = new StringBuilder();
error.AppendLine("Build failed");
foreach (var diagnostic in result.Diagnostics)
{
error.AppendLine(diagnostic.ToString());
}
throw new Exception(error.ToString());
}
memoryStream.Seek(0, SeekOrigin.Begin);
var assemblyStream = new MemoryStream();
memoryStream.CopyTo(assemblyStream);
return assemblyStream;
}
}
catch (Exception e)
{
var error = new StringBuilder();
error.AppendLine("CreateAssembly failed");
error.AppendLine(e.Message);
throw new Exception(error.ToString());
}
}
protected virtual SyntaxTree CreateSyntaxTree(string code)
{
return CSharpSyntaxTree.ParseText(code);
}
protected virtual CSharpCompilationOptions GetCompilationOptions()
{
var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
// Disable 1702 until roslyn turns this off by default
csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions(
new Dictionary<string, ReportDiagnostic>
{
{"CS1701", ReportDiagnostic.Suppress}, // Binding redirects
{"CS1702", ReportDiagnostic.Suppress},
{"CS1705", ReportDiagnostic.Suppress}
});
return csharpCompilationOptions.WithOptimizationLevel(OptimizationLevel.Release);
}
}
}

15
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompilerOptions.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Volo.Abp.TextTemplating.Razor
{
public class AbpRazorTemplateCSharpCompilerOptions
{
public List<PortableExecutableReference> References { get; }
public AbpRazorTemplateCSharpCompilerOptions()
{
References = new List<PortableExecutableReference>();
}
}
}

9
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateConsts.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.TextTemplating.Razor
{
public static class AbpRazorTemplateConsts
{
public const string DefaultNameSpace = "Abp.Razor";
public const string DefaultClassName = "Template";
public const string TypeName = DefaultNameSpace + "." + DefaultClassName;
}
}

12
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpTextTemplatingRazorModule.cs

@ -0,0 +1,12 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.TextTemplating.Razor
{
[DependsOn(
typeof(AbpTextTemplatingAbstractionsModule)
)]
public class AbpTextTemplatingRazorModule : AbpModule
{
}
}

77
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating.Razor
{
public class DefaultAbpCompiledViewProvider : IAbpCompiledViewProvider, ITransientDependency
{
private static readonly ConcurrentDictionary<string, Assembly> CachedAssembles = new ConcurrentDictionary<string, Assembly>();
private readonly AbpCompiledViewProviderOptions _options;
private readonly AbpRazorTemplateCSharpCompiler _razorTemplateCSharpCompiler;
private readonly IAbpRazorProjectEngineFactory _razorProjectEngineFactory;
private readonly ITemplateContentProvider _templateContentProvider;
public DefaultAbpCompiledViewProvider(
IOptions<AbpCompiledViewProviderOptions> options,
IAbpRazorProjectEngineFactory razorProjectEngineFactory,
AbpRazorTemplateCSharpCompiler razorTemplateCSharpCompiler,
ITemplateContentProvider templateContentProvider)
{
_options = options.Value;
_razorProjectEngineFactory = razorProjectEngineFactory;
_razorTemplateCSharpCompiler = razorTemplateCSharpCompiler;
_templateContentProvider = templateContentProvider;
}
public virtual async Task<Assembly> GetAssemblyAsync(TemplateDefinition templateDefinition)
{
async Task<Assembly> CreateAssembly(string content)
{
using (var assemblyStream = await GetAssemblyStreamAsync(templateDefinition, content))
{
return Assembly.Load(await assemblyStream.GetAllBytesAsync());
}
}
var templateContent = await _templateContentProvider.GetContentOrNullAsync(templateDefinition);
return CachedAssembles.GetOrAdd((templateDefinition.Name + templateContent).ToMd5(), await CreateAssembly(templateContent));
}
protected virtual async Task<Stream> GetAssemblyStreamAsync(TemplateDefinition templateDefinition, string templateContent)
{
var razorProjectEngine = await _razorProjectEngineFactory.CreateAsync(builder =>
{
builder.SetNamespace(AbpRazorTemplateConsts.DefaultNameSpace);
builder.ConfigureClass((document, node) =>
{
node.ClassName = AbpRazorTemplateConsts.DefaultClassName;
});
});
var codeDocument = razorProjectEngine.Process(
RazorSourceDocument.Create(templateContent, templateDefinition.Name), null,
new List<RazorSourceDocument>(), new List<TagHelperDescriptor>());
var cSharpDocument = codeDocument.GetCSharpDocument();
var templateReferences = _options.TemplateReferences
.GetOrDefault(templateDefinition.Name)
?.Select(x => x)
.Cast<MetadataReference>()
.ToList();
return _razorTemplateCSharpCompiler.CreateAssembly(cSharpDocument.GeneratedCode, templateDefinition.Name, templateReferences);
}
}
}

20
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpRazorProjectEngineFactory.cs

@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating.Razor
{
public class DefaultAbpRazorProjectEngineFactory : IAbpRazorProjectEngineFactory, ITransientDependency
{
public virtual async Task<RazorProjectEngine> CreateAsync(Action<RazorProjectEngineBuilder> configure = null)
{
return RazorProjectEngine.Create(await CreateRazorConfigurationAsync(), EmptyProjectFileSystem.Empty, configure);
}
protected virtual Task<RazorConfiguration> CreateRazorConfigurationAsync()
{
return Task.FromResult(RazorConfiguration.Default);
}
}
}

30
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/EmptyProjectFileSystem.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
namespace Volo.Abp.TextTemplating.Razor
{
internal class EmptyProjectFileSystem : RazorProjectFileSystem
{
internal static readonly RazorProjectFileSystem Empty = new EmptyProjectFileSystem();
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
NormalizeAndEnsureValidPath(basePath);
return Enumerable.Empty<RazorProjectItem>();
}
[Obsolete("Use GetItem(string path, string fileKind) instead.")]
public override RazorProjectItem GetItem(string path)
{
return GetItem(path, fileKind: null);
}
public override RazorProjectItem GetItem(string path, string fileKind)
{
NormalizeAndEnsureValidPath(path);
return new NotFoundProjectItem(string.Empty, path, fileKind);
}
}
}

10
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpCompiledViewProvider.cs

@ -0,0 +1,10 @@
using System.Reflection;
using System.Threading.Tasks;
namespace Volo.Abp.TextTemplating.Razor
{
public interface IAbpCompiledViewProvider
{
Task<Assembly> GetAssemblyAsync(TemplateDefinition templateDefinition);
}
}

11
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpRazorProjectEngineFactory.cs

@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
namespace Volo.Abp.TextTemplating.Razor
{
public interface IAbpRazorProjectEngineFactory
{
Task<RazorProjectEngine> CreateAsync(Action<RazorProjectEngineBuilder> configure = null);
}
}

34
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IRazorTemplatePage.cs

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.TextTemplating.Razor
{
public interface IRazorTemplatePage<TModel> : IRazorTemplatePage
{
TModel Model { get; set; }
}
public interface IRazorTemplatePage
{
IServiceProvider ServiceProvider { get; set; }
IStringLocalizer Localizer { get; set; }
HtmlEncoder HtmlEncoder { get; set; }
JavaScriptEncoder JavaScriptEncoder { get; set; }
UrlEncoder UrlEncoder { get; set; }
Dictionary<string, object> GlobalContext { get; set; }
string Body { get; set; }
Task ExecuteAsync();
Task<string> GetOutputAsync();
}
}

28
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/NotFoundProjectItem.cs

@ -0,0 +1,28 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Razor.Language;
namespace Volo.Abp.TextTemplating.Razor
{
internal class NotFoundProjectItem : RazorProjectItem
{
public NotFoundProjectItem(string basePath, string path, string fileKind)
{
BasePath = basePath;
FilePath = path;
FileKind = fileKind ?? FileKinds.GetFileKindFromFilePath(path);
}
public override string BasePath { get; }
public override string FilePath { get; }
public override string FileKind { get; }
public override bool Exists => false;
public override string PhysicalPath => throw new NotSupportedException();
public override Stream Read() => throw new NotSupportedException();
}
}

177
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplatePageBase.cs

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.TextTemplating.Razor
{
public abstract class RazorTemplatePageBase<TModel> : RazorTemplatePageBase, IRazorTemplatePage<TModel>
{
public TModel Model { get; set; }
}
public abstract class RazorTemplatePageBase : IRazorTemplatePage
{
public Dictionary<string, object> GlobalContext { get; set; }
public IServiceProvider ServiceProvider { get; set; }
public IStringLocalizer Localizer { get; set; }
public HtmlEncoder HtmlEncoder { get; set; }
public JavaScriptEncoder JavaScriptEncoder { get; set; }
public UrlEncoder UrlEncoder { get; set; }
public string Body { get; set; }
private readonly StringBuilder _stringBuilder = new StringBuilder();
private AttributeInfo _attributeInfo;
public virtual void WriteLiteral(string literal = null)
{
_stringBuilder.Append(literal);
}
public virtual void Write(object obj = null)
{
_stringBuilder.Append(obj);
}
public virtual void BeginWriteAttribute(string name, string prefix, int prefixOffset, string suffix, int suffixOffset, int attributeValuesCount)
{
_attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
WriteLiteral(prefix);
}
public virtual void WriteAttributeValue(string prefix, int prefixOffset, object value, int valueOffset, int valueLength, bool isLiteral)
{
if (_attributeInfo.AttributeValuesCount == 1)
{
if (IsBoolFalseOrNullValue(prefix, value))
{
// Value is either null or the bool 'false' with no prefix; don't render the attribute.
_attributeInfo.Suppressed = true;
return;
}
// We are not omitting the attribute. Write the prefix.
WriteLiteral(_attributeInfo.Prefix);
if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
{
// The value is just the bool 'true', write the attribute name instead of the string 'True'.
value = _attributeInfo.Name;
}
}
// This block handles two cases.
// 1. Single value with prefix.
// 2. Multiple values with or without prefix.
if (value != null)
{
if (!string.IsNullOrEmpty(prefix))
{
WriteLiteral(prefix);
}
WriteUnprefixedAttributeValue(value, isLiteral);
}
_stringBuilder.Append(prefix);
_stringBuilder.Append(value);
}
public virtual void EndWriteAttribute()
{
if (!_attributeInfo.Suppressed)
{
WriteLiteral(_attributeInfo.Suffix);
}
}
public virtual Task ExecuteAsync()
{
return Task.CompletedTask;
}
public virtual Task<string> GetOutputAsync()
{
return Task.FromResult(_stringBuilder.ToString());
}
private void WriteUnprefixedAttributeValue(object value, bool isLiteral)
{
var stringValue = value as string;
// The extra branching here is to ensure that we call the Write*To(string) overload where possible.
if (isLiteral && stringValue != null)
{
WriteLiteral(stringValue);
}
else if (isLiteral)
{
//WriteLiteral(value);
_stringBuilder.Append(value);
}
else if (stringValue != null)
{
Write(stringValue);
}
else
{
Write(value);
}
}
private bool IsBoolFalseOrNullValue(string prefix, object value)
{
return string.IsNullOrEmpty(prefix) && (value == null || (value is bool b && !b));
}
private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value)
{
// If the value is just the bool 'true', use the attribute name as the value.
return string.IsNullOrEmpty(prefix) && (value is bool b && b);
}
private struct AttributeInfo
{
public AttributeInfo(
string name,
string prefix,
int prefixOffset,
string suffix,
int suffixOffset,
int attributeValuesCount)
{
Name = name;
Prefix = prefix;
PrefixOffset = prefixOffset;
Suffix = suffix;
SuffixOffset = suffixOffset;
AttributeValuesCount = attributeValuesCount;
Suppressed = false;
}
public int AttributeValuesCount { get; }
public string Name { get; }
public string Prefix { get; }
public int PrefixOffset { get; }
public string Suffix { get; }
public int SuffixOffset { get; }
public bool Suppressed { get; set; }
}
}
}

168
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer.cs

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
namespace Volo.Abp.TextTemplating.Razor
{
[Dependency(ReplaceServices = true)]
public class RazorTemplateRenderer : ITemplateRenderer, ITransientDependency
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IAbpCompiledViewProvider _abpCompiledViewProvider;
private readonly ITemplateDefinitionManager _templateDefinitionManager;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public RazorTemplateRenderer(
IServiceScopeFactory serviceScopeFactory,
IAbpCompiledViewProvider abpCompiledViewProvider,
ITemplateDefinitionManager templateDefinitionManager,
IStringLocalizerFactory stringLocalizerFactory)
{
_serviceScopeFactory = serviceScopeFactory;
_templateDefinitionManager = templateDefinitionManager;
_stringLocalizerFactory = stringLocalizerFactory;
_abpCompiledViewProvider = abpCompiledViewProvider;
}
public virtual async Task<string> RenderAsync(
[NotNull] string templateName,
[CanBeNull] object model = null,
[CanBeNull] string cultureName = null,
[CanBeNull] Dictionary<string, object> globalContext = null)
{
Check.NotNullOrWhiteSpace(templateName, nameof(templateName));
if (globalContext == null)
{
globalContext = new Dictionary<string, object>();
}
if (cultureName == null)
{
return await RenderInternalAsync(
templateName,
null,
globalContext,
model
);
}
else
{
using (CultureHelper.Use(cultureName))
{
return await RenderInternalAsync(
templateName,
null,
globalContext,
model
);
}
}
}
protected virtual async Task<string> RenderInternalAsync(
string templateName,
string body,
Dictionary<string, object> globalContext,
object model = null)
{
var templateDefinition = _templateDefinitionManager.Get(templateName);
var renderedContent = await RenderSingleTemplateAsync(
templateDefinition,
body,
globalContext,
model
);
if (templateDefinition.Layout != null)
{
renderedContent = await RenderInternalAsync(
templateDefinition.Layout,
renderedContent,
globalContext
);
}
return renderedContent;
}
protected virtual async Task<string> RenderSingleTemplateAsync(
TemplateDefinition templateDefinition,
string body,
Dictionary<string, object> globalContext,
object model = null)
{
return await RenderTemplateContentWithRazorAsync(
templateDefinition,
body,
globalContext,
model
);
}
protected virtual async Task<string> RenderTemplateContentWithRazorAsync(
TemplateDefinition templateDefinition,
string body,
Dictionary<string, object> globalContext,
object model = null)
{
var assembly = await _abpCompiledViewProvider.GetAssemblyAsync(templateDefinition);
var templateType = assembly.GetType(AbpRazorTemplateConsts.TypeName);
var template = (IRazorTemplatePage) Activator.CreateInstance(templateType);
using (var scope = _serviceScopeFactory.CreateScope())
{
var modelType = templateType
.GetInterfaces()
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRazorTemplatePage<>))
.Select(x => x.GenericTypeArguments.FirstOrDefault())
.FirstOrDefault();
if (modelType != null)
{
GetType().GetMethod(nameof(SetModel), BindingFlags.Instance | BindingFlags.NonPublic)
?.MakeGenericMethod(modelType).Invoke(this, new[] {template, model});
}
template.ServiceProvider = scope.ServiceProvider;
template.Localizer = GetLocalizerOrNull(templateDefinition);
template.HtmlEncoder = scope.ServiceProvider.GetService<HtmlEncoder>();
template.JavaScriptEncoder = scope.ServiceProvider.GetService<JavaScriptEncoder>();
template.UrlEncoder = scope.ServiceProvider.GetService<UrlEncoder>();
template.Body = body;
template.GlobalContext = globalContext;
await template.ExecuteAsync();
return await template.GetOutputAsync();
}
}
private void SetModel<TModel>(IRazorTemplatePage razorTemplatePage, object model = null)
{
if (razorTemplatePage is IRazorTemplatePage<TModel> razorTemplatePageWithModel)
{
razorTemplatePageWithModel.Model = (TModel)model;
}
}
private IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition)
{
if (templateDefinition.LocalizationResource != null)
{
return _stringLocalizerFactory.Create(templateDefinition.LocalizationResource);
}
return _stringLocalizerFactory.CreateDefaultOrNull();
}
}
}

3
framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

19
framework/src/Volo.Abp.TextTemplating.Scriban/Volo.Abp.TextTemplating.Scriban.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Scriban" Version="3.6.0" />
</ItemGroup>
</Project>

12
framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/AbpTextTemplatingScribanModule.cs

@ -0,0 +1,12 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.TextTemplating.Scriban
{
[DependsOn(
typeof(AbpTextTemplatingAbstractionsModule)
)]
public class AbpTextTemplatingScribanModule : AbpModule
{
}
}

6
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs → framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateLocalizer.cs

@ -7,13 +7,13 @@ using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;
namespace Volo.Abp.TextTemplating
namespace Volo.Abp.TextTemplating.Scriban
{
public class TemplateLocalizer : IScriptCustomFunction
public class ScribanTemplateLocalizer : IScriptCustomFunction
{
private readonly IStringLocalizer _localizer;
public TemplateLocalizer(IStringLocalizer localizer)
public ScribanTemplateLocalizer(IStringLocalizer localizer)
{
_localizer = localizer;
}

12
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs → framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer.cs

@ -1,23 +1,21 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Scriban;
using Scriban.Runtime;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
namespace Volo.Abp.TextTemplating
namespace Volo.Abp.TextTemplating.Scriban
{
public class TemplateRenderer : ITemplateRenderer, ITransientDependency
public class ScribanTemplateRenderer : ITemplateRenderer, ITransientDependency
{
private readonly ITemplateContentProvider _templateContentProvider;
private readonly ITemplateDefinitionManager _templateDefinitionManager;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public TemplateRenderer(
public ScribanTemplateRenderer(
ITemplateContentProvider templateContentProvider,
ITemplateDefinitionManager templateDefinitionManager,
IStringLocalizerFactory stringLocalizerFactory)
@ -140,7 +138,7 @@ namespace Volo.Abp.TextTemplating
var localizer = GetLocalizerOrNull(templateDefinition);
if (localizer != null)
{
scriptObject.SetValue("L", new TemplateLocalizer(localizer), true);
scriptObject.SetValue("L", new ScribanTemplateLocalizer(localizer), true);
}
context.PushGlobal(scriptObject);

13
framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj

@ -5,22 +5,11 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.TextTemplating</AssemblyName>
<PackageId>Volo.Abp.TextTemplating</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Scriban" Version="3.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
<ProjectReference Include="..\Volo.Abp.TextTemplating.Scriban\Volo.Abp.TextTemplating.Scriban.csproj" />
</ItemGroup>
</Project>

41
framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs

@ -1,46 +1,13 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.Modularity;
using Volo.Abp.TextTemplating.Scriban;
namespace Volo.Abp.TextTemplating
{
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpLocalizationAbstractionsModule)
)]
typeof(AbpTextTemplatingScribanModule)
)]
public class AbpTextTemplatingModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddProvidersAndContributors(context.Services);
}
private static void AutoAddProvidersAndContributors(IServiceCollection services)
{
var definitionProviders = new List<Type>();
var contentContributors = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
definitionProviders.Add(context.ImplementationType);
}
if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType))
{
contentContributors.Add(context.ImplementationType);
}
});
services.Configure<AbpTextTemplatingOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(definitionProviders);
options.ContentContributors.AddIfNotContains(contentContributors);
});
}
}
}

23
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo.Abp.TextTemplating.Razor.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\TextTemplating\Razor\SampleTemplates\**\*.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Remove="Volo\Abp\TextTemplating\Razor\SampleTemplates\**\*.cshtml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Razor\Volo.Abp.TextTemplating.Razor.csproj" />
<ProjectReference Include="..\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
</Project>

48
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.TextTemplating.Razor.SampleTemplates;
using Xunit;
namespace Volo.Abp.TextTemplating.Razor
{
public class AbpCompiledViewProviderOptions_Tests : TemplateDefinitionTests<RazorTextTemplatingTestModule>
{
private readonly IAbpCompiledViewProvider _compiledViewProvider;
private readonly ITemplateDefinitionManager _templateDefinitionManager;
public AbpCompiledViewProviderOptions_Tests()
{
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
_compiledViewProvider = GetRequiredService<IAbpCompiledViewProvider>();
}
protected override void AfterAddApplication(IServiceCollection services)
{
services.Configure<AbpCompiledViewProviderOptions>(options =>
{
options.TemplateReferences.Add(RazorTestTemplates.TestTemplate, new List<Assembly>()
{
Assembly.Load("Microsoft.Extensions.Logging.Abstractions")
}
.Select(x => MetadataReference.CreateFromFile(x.Location))
.ToList());
});
base.AfterAddApplication(services);
}
[Fact]
public async Task Custom_TemplateReferences_Test()
{
var templateDefinition = _templateDefinitionManager.GetOrNull(RazorTestTemplates.TestTemplate);
var assembly = await _compiledViewProvider.GetAssemblyAsync(templateDefinition);
assembly.GetReferencedAssemblies().ShouldContain(x => x.Name == "Microsoft.Extensions.Logging.Abstractions");
}
}
}

26
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorLocalizedTemplateContentReaderFactory_Tests.cs

@ -0,0 +1,26 @@
using System;
using System.IO;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.TextTemplating.VirtualFiles;
namespace Volo.Abp.TextTemplating.Razor
{
public class RazorLocalizedTemplateContentReaderFactory_Tests : LocalizedTemplateContentReaderFactory_Tests<RazorTextTemplatingTestModule>
{
public RazorLocalizedTemplateContentReaderFactory_Tests()
{
LocalizedTemplateContentReaderFactory = new LocalizedTemplateContentReaderFactory(
new PhysicalFileVirtualFileProvider(
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),
"Volo", "Abp", "TextTemplating", "Razor"))));
WelcomeEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" +
Environment.NewLine +
"Welcome @Model.Name to the abp.io!";
WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" +
Environment.NewLine +
"Merhaba @Model.Name, abp.io'ya hoşgeldiniz!";
}
}
}

7
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateDefinitionTests.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.TextTemplating.Razor
{
public class RazorTemplateDefinitionTests : TemplateDefinitionTests<RazorTextTemplatingTestModule>
{
}
}

145
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer_Tests.cs

@ -0,0 +1,145 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.TextTemplating.Razor
{
public class RazorTemplateRenderer_Tests : AbpTextTemplatingTestBase<RazorTextTemplatingTestModule>
{
private readonly ITemplateRenderer _templateRenderer;
public RazorTemplateRenderer_Tests()
{
_templateRenderer = GetRequiredService<ITemplateRenderer>();
}
[Fact]
public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures()
{
(await _templateRenderer.RenderAsync(
TestTemplates.WelcomeEmail,
model: new WelcomeEmailModel()
{
Name = "John"
},
cultureName: "en"
)).ShouldBe("Welcome John to the abp.io!");
(await _templateRenderer.RenderAsync(
TestTemplates.WelcomeEmail,
model: new WelcomeEmailModel()
{
Name = "John"
},
cultureName: "tr"
)).ShouldBe("Merhaba John, abp.io'ya hoşgeldiniz!");
//"en-US" fallbacks to "en" since "en-US" doesn't exists and "en" is the fallback culture
(await _templateRenderer.RenderAsync(
TestTemplates.WelcomeEmail,
model: new WelcomeEmailModel()
{
Name = "John"
},
cultureName: "en-US"
)).ShouldBe("Welcome John to the abp.io!");
//"fr" fallbacks to "en" since "fr" doesn't exists and "en" is the default culture
(await _templateRenderer.RenderAsync(
TestTemplates.WelcomeEmail,
model: new WelcomeEmailModel()
{
Name = "John"
},
cultureName: "fr"
)).ShouldBe("Welcome John to the abp.io!");
}
[Fact]
public async Task Should_Get_Rendered_Localized_Template_Content_With_Stronly_Typed_Model()
{
(await _templateRenderer.RenderAsync(
TestTemplates.WelcomeEmail,
model: new WelcomeEmailModel("John"),
cultureName: "en"
)).ShouldBe("Welcome John to the abp.io!");
}
[Fact]
public async Task Should_Get_Rendered_Inline_Localized_Template()
{
(await _templateRenderer.RenderAsync(
TestTemplates.ForgotPasswordEmail,
new ForgotPasswordEmailModel("John"),
cultureName: "en"
)).ShouldBe("*BEGIN*Hello John, how are you?. Please click to the following link to get an email to reset your password!*END*");
(await _templateRenderer.RenderAsync(
TestTemplates.ForgotPasswordEmail,
new ForgotPasswordEmailModel("John"),
cultureName: "tr"
)).ShouldBe("*BEGIN*Merhaba John, nasılsın?. Please click to the following link to get an email to reset your password!*END*");
}
[Fact]
public async Task Should_Get_Localized_Numbers()
{
(await _templateRenderer.RenderAsync(
TestTemplates.ShowDecimalNumber,
new ShowDecimalNumberModel(123.45M),
cultureName: "en"
)).ShouldBe("*BEGIN*123.45*END*");
(await _templateRenderer.RenderAsync(
TestTemplates.ShowDecimalNumber,
new ShowDecimalNumberModel(123.45M),
cultureName: "de"
)).ShouldBe("*BEGIN*123,45*END*");
}
public class WelcomeEmailModel
{
public string Name { get; set; }
public WelcomeEmailModel()
{
}
public WelcomeEmailModel(string name)
{
Name = name;
}
}
public class ForgotPasswordEmailModel
{
public string Name { get; set; }
public ForgotPasswordEmailModel()
{
}
public ForgotPasswordEmailModel(string name)
{
Name = name;
}
}
public class ShowDecimalNumberModel
{
public decimal Amount { get; set; }
public ShowDecimalNumberModel()
{
}
public ShowDecimalNumberModel(decimal amount)
{
Amount = amount;
}
}
}
}

24
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs

@ -0,0 +1,24 @@
using Volo.Abp.TextTemplating.Razor.SampleTemplates;
namespace Volo.Abp.TextTemplating.Razor
{
public class RazorTestTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.GetOrNull(TestTemplates.WelcomeEmail)?
.WithVirtualFilePath("/SampleTemplates/WelcomeEmail", false);
context.GetOrNull(TestTemplates.ForgotPasswordEmail)?
.WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.cshtml", true);
context.GetOrNull(TestTemplates.TestTemplateLayout1)?
.WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.cshtml", true);
context.GetOrNull(TestTemplates.ShowDecimalNumber)?
.WithVirtualFilePath("/SampleTemplates/ShowDecimalNumber.cshtml", true);
context.Add(new TemplateDefinition(RazorTestTemplates.TestTemplate).WithVirtualFilePath("/SampleTemplates/TestTemplate.cshtml", true));
}
}
}

26
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTextTemplatingTestModule.cs

@ -0,0 +1,26 @@
using Microsoft.CodeAnalysis;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.TextTemplating.Razor
{
[DependsOn(
typeof(AbpTextTemplatingRazorModule),
typeof(AbpTextTemplatingTestModule)
)]
public class RazorTextTemplatingTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<RazorTextTemplatingTestModule>("Volo.Abp.TextTemplating.Razor");
});
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
{
options.References.Add(MetadataReference.CreateFromFile(typeof(RazorTextTemplatingTestModule).Assembly.Location));
});
}
}
}

23
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorVirtualFileTemplateContributor_Tests.cs

@ -0,0 +1,23 @@
using System;
using Volo.Abp.TextTemplating.VirtualFiles;
namespace Volo.Abp.TextTemplating.Razor
{
public class RazorVirtualFileTemplateContributor_Tests : VirtualFileTemplateContributor_Tests<RazorTextTemplatingTestModule>
{
public RazorVirtualFileTemplateContributor_Tests()
{
WelcomeEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" +
Environment.NewLine +
"Welcome @Model.Name to the abp.io!";
WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" +
Environment.NewLine +
"Merhaba @Model.Name, abp.io'ya hoşgeldiniz!";
ForgotPasswordEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ForgotPasswordEmailModel>" +
Environment.NewLine +
"@Localizer[\"HelloText\", Model.Name], @Localizer[\"HowAreYou\"]. Please click to the following link to get an email to reset your password!";
}
}
}

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ForgotPasswordEmail.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ForgotPasswordEmailModel>
@Localizer["HelloText", Model.Name], @Localizer["HowAreYou"]. Please click to the following link to get an email to reset your password!

7
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.TextTemplating.Razor.SampleTemplates
{
public static class RazorTestTemplates
{
public const string TestTemplate = "TestTemplate";
}
}

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ShowDecimalNumber.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ShowDecimalNumberModel>
@Model.Amount

6
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplate.cshtml

@ -0,0 +1,6 @@
@using Microsoft.Extensions.DependencyInjection
@using Microsoft.Extensions.Logging
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
@{
var loggerFactory = ServiceProvider.GetService<ILoggerFactory>();
}

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplateLayout1.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
*BEGIN*@Body*END*

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/en.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>
Welcome @Model.Name to the abp.io!

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/tr.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>
Merhaba @Model.Name, abp.io'ya hoşgeldiniz!

23
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo.Abp.TextTemplating.Scriban.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\TextTemplating\Scriban\SampleTemplates\**\*.tpl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Remove="Volo\Abp\TextTemplating\Scriban\SampleTemplates\**\*.tpl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Scriban\Volo.Abp.TextTemplating.Scriban.csproj" />
<ProjectReference Include="..\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
</Project>

0
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ForgotPasswordEmail.tpl

0
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ShowDecimalNumber.tpl → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ShowDecimalNumber.tpl

0
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/TestTemplateLayout1.tpl

0
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/en.tpl

0
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/tr.tpl

20
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanLocalizedTemplateContentReaderFactory_Tests.cs

@ -0,0 +1,20 @@
using System.IO;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.TextTemplating.VirtualFiles;
namespace Volo.Abp.TextTemplating.Scriban
{
public class ScribanLocalizedTemplateContentReaderFactory_Tests : LocalizedTemplateContentReaderFactory_Tests<ScribanTextTemplatingTestModule>
{
public ScribanLocalizedTemplateContentReaderFactory_Tests()
{
LocalizedTemplateContentReaderFactory = new LocalizedTemplateContentReaderFactory(
new PhysicalFileVirtualFileProvider(
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),
"Volo", "Abp", "TextTemplating", "Scriban"))));
WelcomeEmailEnglishContent = "Welcome {{model.name}} to the abp.io!";
WelcomeEmailTurkishContent = "Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!";
}
}
}

7
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateDefinitionTests.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.TextTemplating.Scriban
{
public class ScribanTemplateDefinitionTests : TemplateDefinitionTests<ScribanTextTemplatingTestModule>
{
}
}

8
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs → framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer_Tests.cs

@ -3,13 +3,13 @@ using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.TextTemplating
namespace Volo.Abp.TextTemplating.Scriban
{
public class TemplateRenderer_Tests : AbpTextTemplatingTestBase
public class ScribanTemplateRenderer_Tests : AbpTextTemplatingTestBase<ScribanTextTemplatingTestModule>
{
private readonly ITemplateRenderer _templateRenderer;
public TemplateRenderer_Tests()
public ScribanTemplateRenderer_Tests()
{
_templateRenderer = GetRequiredService<ITemplateRenderer>();
}
@ -91,7 +91,7 @@ namespace Volo.Abp.TextTemplating
cultureName: "tr"
)).ShouldBe("*BEGIN*Merhaba John, nasılsın?. Please click to the following link to get an email to reset your password!*END*");
}
[Fact]
public async Task Should_Get_Localized_Numbers()
{

20
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs

@ -0,0 +1,20 @@
namespace Volo.Abp.TextTemplating.Scriban
{
public class ScribanTestTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.GetOrNull(TestTemplates.WelcomeEmail)?
.WithVirtualFilePath("/SampleTemplates/WelcomeEmail", false);
context.GetOrNull(TestTemplates.ForgotPasswordEmail)?
.WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl", true);
context.GetOrNull(TestTemplates.TestTemplateLayout1)?
.WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl", true);
context.GetOrNull(TestTemplates.ShowDecimalNumber)?
.WithVirtualFilePath("/SampleTemplates/ShowDecimalNumber.tpl", true);
}
}
}

20
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTextTemplatingTestModule.cs

@ -0,0 +1,20 @@
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.TextTemplating.Scriban
{
[DependsOn(
typeof(AbpTextTemplatingScribanModule),
typeof(AbpTextTemplatingTestModule)
)]
public class ScribanTextTemplatingTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<ScribanTextTemplatingTestModule>("Volo.Abp.TextTemplating.Scriban");
});
}
}
}

14
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanVirtualFileTemplateContributor_Tests.cs

@ -0,0 +1,14 @@
using Volo.Abp.TextTemplating.VirtualFiles;
namespace Volo.Abp.TextTemplating.Scriban
{
public class ScribanVirtualFileTemplateContributor_Tests : VirtualFileTemplateContributor_Tests<ScribanTextTemplatingTestModule>
{
public ScribanVirtualFileTemplateContributor_Tests()
{
WelcomeEmailEnglishContent = "Welcome {{model.name}} to the abp.io!";
WelcomeEmailTurkishContent = "Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!";
ForgotPasswordEmailEnglishContent = "{{L \"HelloText\" model.name}}, {{L \"HowAreYou\" }}. Please click to the following link to get an email to reset your password!";
}
}
}

5
framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj

@ -10,15 +10,12 @@
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\TextTemplating\Localization\**\*.json" />
<None Remove="Volo\Abp\TextTemplating\Localization\**\*.json" />
<EmbeddedResource Include="Volo\Abp\TextTemplating\SampleTemplates\**\*.tpl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>

4
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs

@ -4,7 +4,7 @@ using Xunit;
namespace Volo.Abp.TextTemplating
{
public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase
public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase<AbpTextTemplatingTestModule>
{
private readonly AbpTextTemplatingOptions _options;
@ -21,4 +21,4 @@ namespace Volo.Abp.TextTemplating
.ShouldContain(typeof(TestTemplateDefinitionProvider));
}
}
}
}

6
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs

@ -1,8 +1,10 @@
using Volo.Abp.Testing;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
namespace Volo.Abp.TextTemplating
{
public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest<AbpTextTemplatingTestModule>
public abstract class AbpTextTemplatingTestBase<TStartupModule> : AbpIntegratedTest<TStartupModule>
where TStartupModule : IAbpModule
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{

2
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs

@ -7,7 +7,7 @@ using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.TextTemplating
{
[DependsOn(
typeof(AbpTextTemplatingModule),
typeof(AbpTextTemplatingAbstractionsModule),
typeof(AbpTestBaseModule),
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule)

20
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs

@ -1,25 +1,27 @@
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.TextTemplating
{
public class TemplateDefinitionTests : AbpTextTemplatingTestBase
public abstract class TemplateDefinitionTests<TStartupModule> : AbpTextTemplatingTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private readonly ITemplateDefinitionManager _templateDefinitionManager;
protected readonly ITemplateDefinitionManager TemplateDefinitionManager;
public TemplateDefinitionTests()
protected TemplateDefinitionTests()
{
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
TemplateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
}
[Fact]
public void Should_Retrieve_Template_Definition_By_Name()
{
var welcomeEmailTemplate = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail);
var welcomeEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail);
welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail);
welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse();
var forgotPasswordEmailTemplate = _templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail);
var forgotPasswordEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail);
forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail);
forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue();
}
@ -27,15 +29,15 @@ namespace Volo.Abp.TextTemplating
[Fact]
public void Should_Get_Null_If_Template_Not_Found()
{
var definition = _templateDefinitionManager.GetOrNull("undefined-template");
var definition = TemplateDefinitionManager.GetOrNull("undefined-template");
definition.ShouldBeNull();
}
[Fact]
public void Should_Retrieve_All_Template_Definitions()
{
var definitions = _templateDefinitionManager.GetAll();
var definitions = TemplateDefinitionManager.GetAll();
definitions.Count.ShouldBeGreaterThan(1);
}
}
}
}

10
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs

@ -10,7 +10,7 @@ namespace Volo.Abp.TextTemplating
new TemplateDefinition(
TestTemplates.WelcomeEmail,
defaultCultureName: "en"
).WithVirtualFilePath("/SampleTemplates/WelcomeEmail", false)
)
);
context.Add(
@ -18,22 +18,22 @@ namespace Volo.Abp.TextTemplating
TestTemplates.ForgotPasswordEmail,
localizationResource: typeof(TestLocalizationSource),
layout: TestTemplates.TestTemplateLayout1
).WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl", true)
)
);
context.Add(
new TemplateDefinition(
TestTemplates.TestTemplateLayout1,
isLayout: true
).WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl", true)
)
);
context.Add(
new TemplateDefinition(
TestTemplates.ShowDecimalNumber,
localizationResource: typeof(TestLocalizationSource),
layout: TestTemplates.TestTemplateLayout1
).WithVirtualFilePath("/SampleTemplates/ShowDecimalNumber.tpl", true)
)
);
}
}

29
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs

@ -1,37 +1,36 @@
using System.IO;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Xunit;
namespace Volo.Abp.TextTemplating.VirtualFiles
{
public class LocalizedTemplateContentReaderFactory_Tests: AbpTextTemplatingTestBase
public abstract class LocalizedTemplateContentReaderFactory_Tests<TStartupModule> : AbpTextTemplatingTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private readonly ITemplateDefinitionManager _templateDefinitionManager;
protected readonly ITemplateDefinitionManager TemplateDefinitionManager;
protected LocalizedTemplateContentReaderFactory LocalizedTemplateContentReaderFactory;
protected string WelcomeEmailEnglishContent;
protected string WelcomeEmailTurkishContent;
public LocalizedTemplateContentReaderFactory_Tests()
protected LocalizedTemplateContentReaderFactory_Tests()
{
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
TemplateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
}
[Fact]
public async Task Create_Should_Work_With_PhysicalFileProvider()
{
var localizedTemplateContentReaderFactory = new LocalizedTemplateContentReaderFactory(
new PhysicalFileVirtualFileProvider(
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),
"Volo", "Abp", "TextTemplating"))));
var reader = await LocalizedTemplateContentReaderFactory.CreateAsync(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail));
var reader = await localizedTemplateContentReaderFactory.CreateAsync(_templateDefinitionManager.Get(TestTemplates.WelcomeEmail));
reader.GetContentOrNull("en").ShouldBe("Welcome {{model.name}} to the abp.io!");
reader.GetContentOrNull("tr").ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!");
reader.GetContentOrNull("en").ShouldBe(WelcomeEmailEnglishContent);
reader.GetContentOrNull("tr").ShouldBe(WelcomeEmailTurkishContent);
}
class PhysicalFileVirtualFileProvider : IVirtualFileProvider
public class PhysicalFileVirtualFileProvider : IVirtualFileProvider
{
private readonly PhysicalFileProvider _physicalFileProvider;

35
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs

@ -1,45 +1,50 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.TextTemplating.VirtualFiles
{
public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase
public abstract class VirtualFileTemplateContributor_Tests<TStartupModule> : AbpTextTemplatingTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private readonly ITemplateDefinitionManager _templateDefinitionManager;
private readonly VirtualFileTemplateContentContributor _virtualFileTemplateContentContributor;
protected readonly ITemplateDefinitionManager TemplateDefinitionManager;
protected readonly VirtualFileTemplateContentContributor VirtualFileTemplateContentContributor;
protected string WelcomeEmailEnglishContent;
protected string WelcomeEmailTurkishContent;
protected string ForgotPasswordEmailEnglishContent;
public VirtualFileTemplateContributor_Tests()
protected VirtualFileTemplateContributor_Tests()
{
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
_virtualFileTemplateContentContributor = GetRequiredService<VirtualFileTemplateContentContributor>();
TemplateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
VirtualFileTemplateContentContributor = GetRequiredService<VirtualFileTemplateContentContributor>();
}
[Fact]
public async Task Should_Get_Localized_Content_By_Culture()
{
(await _virtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(_templateDefinitionManager.Get(TestTemplates.WelcomeEmail),
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail),
ServiceProvider,
"en")))
.ShouldBe("Welcome {{model.name}} to the abp.io!");
.ShouldBe(WelcomeEmailEnglishContent);
(await _virtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(_templateDefinitionManager.Get(TestTemplates.WelcomeEmail),
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail),
ServiceProvider,
"tr")))
.ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!");
.ShouldBe(WelcomeEmailTurkishContent);
}
[Fact]
public async Task Should_Get_Non_Localized_Template_Content()
{
(await _virtualFileTemplateContentContributor.GetOrNullAsync(
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(
_templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail),
TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail),
ServiceProvider,
null)))
.ShouldBe("{{L \"HelloText\" model.name}}, {{L \"HowAreYou\" }}. Please click to the following link to get an email to reset your password!");
.ShouldBe(ForgotPasswordEmailEnglishContent);
}
}
}

3
nupkg/common.ps1

@ -138,7 +138,10 @@ $projects = (
"framework/src/Volo.Abp.Sms.Aliyun",
"framework/src/Volo.Abp.Specifications",
"framework/src/Volo.Abp.TestBase",
"framework/src/Volo.Abp.TextTemplating.Abstractions",
"framework/src/Volo.Abp.TextTemplating",
"framework/src/Volo.Abp.TextTemplating.Razor",
"framework/src/Volo.Abp.TextTemplating.Scriban",
"framework/src/Volo.Abp.Threading",
"framework/src/Volo.Abp.Timing",
"framework/src/Volo.Abp.UI",

Loading…
Cancel
Save