diff --git a/docs/en/Text-Templating-Razor.md b/docs/en/Text-Templating-Razor.md new file mode 100644 index 0000000000..700d262b9b --- /dev/null +++ b/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(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(options => + { + //Hello is template name. + options.TemplateReferences.Add("Hello", new List() + { + 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`. +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 +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(options => +{ + options.FileSets.AddEmbedded("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 +@Localizer["ResetMyPassword", Model.Name] +```` + +`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 +Hi john, Click here to reset your password +```` + +> 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 + + + + + + + @Body + + +```` + +* 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 + { + {"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 + + + + + + +

This my header

+ + @Body + +
+ This is my footer... +
+ + +```` + +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 `true` into the `...` section of your `.csproj` file. +* Add the following code into your `.csproj` file: + +````xml + + + + +```` + +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(options => +{ + options.FileSets.AddEmbedded(); +}); +``` + +`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 +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 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). diff --git a/docs/en/Text-Templating-Scriban.md b/docs/en/Text-Templating-Scriban.md new file mode 100644 index 0000000000..4d9be31dc8 --- /dev/null +++ b/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(options => +{ + options.FileSets.AddEmbedded("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: + +```` +{%{{{L "ResetMyPassword" model.name}}}%} +```` + +`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 +Hi john, Click here to reset your password +```` + +> 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 + + + + + + + {%{{{content}}}%} + + +```` + +* 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 + { + {"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 + + + + + + +

This my header

+ + {%{{{content}}}%} + +
+ This is my footer... +
+ + +```` + +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 `true` into the `...` section of your `.csproj` file. +* Add the following code into your `.csproj` file: + +````xml + + + + +```` + +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(options => +{ + options.FileSets.AddEmbedded(); +}); +``` + +`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 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). diff --git a/docs/en/Text-Templating.md b/docs/en/Text-Templating.md index bdea7cd00b..c9b03cd940 100644 --- a/docs/en/Text-Templating.md +++ b/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(options => -{ - options.FileSets.AddEmbedded("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: - -```` -{%{{{L "ResetMyPassword" model.name}}}%} -```` - -`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 -Hi john, Click here to reset your password -```` - -> 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 - - - - - - - {%{{{content}}}%} - - -```` - -* 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 - { - {"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 - - - - - - -

This my header

- - {%{{{content}}}%} - -
- This is my footer... -
- - -```` - -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 `true` into the `...` section of your `.csproj` file. -* Add the following code into your `.csproj` file: - -````xml - - - - -```` - -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(options => -{ - options.FileSets.AddEmbedded(); -}); -``` - -`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 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 diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 4d2b2419c2..70eb549b1a 100644 --- a/docs/en/docs-nav.json +++ b/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", diff --git a/docs/en/images/hello-template-razor.png b/docs/en/images/hello-template-razor.png new file mode 100644 index 0000000000..1e0e39fcec Binary files /dev/null and b/docs/en/images/hello-template-razor.png differ diff --git a/docs/en/images/multiple-file-template-razor.png b/docs/en/images/multiple-file-template-razor.png new file mode 100644 index 0000000000..85f87e84f4 Binary files /dev/null and b/docs/en/images/multiple-file-template-razor.png differ diff --git a/docs/en/images/replace-email-layout-razor.png b/docs/en/images/replace-email-layout-razor.png new file mode 100644 index 0000000000..84ff6c1080 Binary files /dev/null and b/docs/en/images/replace-email-layout-razor.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 67d2fa2573..b3af9241c4 100644 --- a/framework/Volo.Abp.sln +++ b/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} diff --git a/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Abstractions/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo.Abp.TextTemplating.Abstractions.csproj b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo.Abp.TextTemplating.Abstractions.csproj new file mode 100644 index 0000000000..649bdc2912 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo.Abp.TextTemplating.Abstractions.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.0 + + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingAbstractionsModule.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingAbstractionsModule.cs new file mode 100644 index 0000000000..bb76b299b2 --- /dev/null +++ b/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(); + var contentContributors = new List(); + + 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(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + options.ContentContributors.AddIfNotContains(contentContributors); + }); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentContributor.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentContributor.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentProvider.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateContentProvider.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateRenderer.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/ITemplateRenderer.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentProvider.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateContentProvider.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinition.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinition.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs similarity index 96% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs index 981d1f8819..cc189e1b3b 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs +++ b/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; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs similarity index 100% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs similarity index 80% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs rename to framework/src/Volo.Abp.TextTemplating.Abstractions/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 6fc7ff4847..27458d02cc 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/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 _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()); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Razor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo.Abp.TextTemplating.Razor.csproj b/framework/src/Volo.Abp.TextTemplating.Razor/Volo.Abp.TextTemplating.Razor.csproj new file mode 100644 index 0000000000..f4d16e630c --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo.Abp.TextTemplating.Razor.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions.cs new file mode 100644 index 0000000000..6da3e8b443 --- /dev/null +++ b/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> TemplateReferences { get; } + + public AbpCompiledViewProviderOptions() + { + TemplateReferences = new Dictionary>(); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompiler.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompiler.cs new file mode 100644 index 0000000000..55b0d10f17 --- /dev/null +++ b/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 options) + { + Options = options.Value; + } + + private static IEnumerable DefaultReferences => new List() + { + 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 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 + { + {"CS1701", ReportDiagnostic.Suppress}, // Binding redirects + {"CS1702", ReportDiagnostic.Suppress}, + {"CS1705", ReportDiagnostic.Suppress} + }); + + return csharpCompilationOptions.WithOptimizationLevel(OptimizationLevel.Release); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompilerOptions.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateCSharpCompilerOptions.cs new file mode 100644 index 0000000000..20c1e391e0 --- /dev/null +++ b/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 References { get; } + + public AbpRazorTemplateCSharpCompilerOptions() + { + References = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateConsts.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpRazorTemplateConsts.cs new file mode 100644 index 0000000000..7701a89630 --- /dev/null +++ b/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; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpTextTemplatingRazorModule.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/AbpTextTemplatingRazorModule.cs new file mode 100644 index 0000000000..318a356b93 --- /dev/null +++ b/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 + { + + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs new file mode 100644 index 0000000000..c743252456 --- /dev/null +++ b/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 CachedAssembles = new ConcurrentDictionary(); + + private readonly AbpCompiledViewProviderOptions _options; + private readonly AbpRazorTemplateCSharpCompiler _razorTemplateCSharpCompiler; + private readonly IAbpRazorProjectEngineFactory _razorProjectEngineFactory; + private readonly ITemplateContentProvider _templateContentProvider; + + public DefaultAbpCompiledViewProvider( + IOptions options, + IAbpRazorProjectEngineFactory razorProjectEngineFactory, + AbpRazorTemplateCSharpCompiler razorTemplateCSharpCompiler, + ITemplateContentProvider templateContentProvider) + { + _options = options.Value; + + _razorProjectEngineFactory = razorProjectEngineFactory; + _razorTemplateCSharpCompiler = razorTemplateCSharpCompiler; + _templateContentProvider = templateContentProvider; + } + + public virtual async Task GetAssemblyAsync(TemplateDefinition templateDefinition) + { + async Task 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 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(), new List()); + + var cSharpDocument = codeDocument.GetCSharpDocument(); + + var templateReferences = _options.TemplateReferences + .GetOrDefault(templateDefinition.Name) + ?.Select(x => x) + .Cast() + .ToList(); + + return _razorTemplateCSharpCompiler.CreateAssembly(cSharpDocument.GeneratedCode, templateDefinition.Name, templateReferences); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpRazorProjectEngineFactory.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpRazorProjectEngineFactory.cs new file mode 100644 index 0000000000..eb6d851f49 --- /dev/null +++ b/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 CreateAsync(Action configure = null) + { + return RazorProjectEngine.Create(await CreateRazorConfigurationAsync(), EmptyProjectFileSystem.Empty, configure); + } + + protected virtual Task CreateRazorConfigurationAsync() + { + return Task.FromResult(RazorConfiguration.Default); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/EmptyProjectFileSystem.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/EmptyProjectFileSystem.cs new file mode 100644 index 0000000000..496e86e977 --- /dev/null +++ b/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 EnumerateItems(string basePath) + { + NormalizeAndEnsureValidPath(basePath); + return Enumerable.Empty(); + } + + [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); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpCompiledViewProvider.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpCompiledViewProvider.cs new file mode 100644 index 0000000000..ba5e96d2fa --- /dev/null +++ b/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 GetAssemblyAsync(TemplateDefinition templateDefinition); + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpRazorProjectEngineFactory.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IAbpRazorProjectEngineFactory.cs new file mode 100644 index 0000000000..c96b171f59 --- /dev/null +++ b/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 CreateAsync(Action configure = null); + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IRazorTemplatePage.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/IRazorTemplatePage.cs new file mode 100644 index 0000000000..fc386c3353 --- /dev/null +++ b/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 : 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 GlobalContext { get; set; } + + string Body { get; set; } + + Task ExecuteAsync(); + + Task GetOutputAsync(); + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/NotFoundProjectItem.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/NotFoundProjectItem.cs new file mode 100644 index 0000000000..735522bd09 --- /dev/null +++ b/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(); + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplatePageBase.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplatePageBase.cs new file mode 100644 index 0000000000..9854775a25 --- /dev/null +++ b/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 : RazorTemplatePageBase, IRazorTemplatePage + { + public TModel Model { get; set; } + } + + public abstract class RazorTemplatePageBase : IRazorTemplatePage + { + public Dictionary 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 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; } + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer.cs new file mode 100644 index 0000000000..db06e67d4a --- /dev/null +++ b/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 RenderAsync( + [NotNull] string templateName, + [CanBeNull] object model = null, + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null) + { + Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + + if (globalContext == null) + { + globalContext = new Dictionary(); + } + + 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 RenderInternalAsync( + string templateName, + string body, + Dictionary 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 RenderSingleTemplateAsync( + TemplateDefinition templateDefinition, + string body, + Dictionary globalContext, + object model = null) + { + return await RenderTemplateContentWithRazorAsync( + templateDefinition, + body, + globalContext, + model + ); + } + + protected virtual async Task RenderTemplateContentWithRazorAsync( + TemplateDefinition templateDefinition, + string body, + Dictionary 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(); + template.JavaScriptEncoder = scope.ServiceProvider.GetService(); + template.UrlEncoder = scope.ServiceProvider.GetService(); + template.Body = body; + template.GlobalContext = globalContext; + + await template.ExecuteAsync(); + + return await template.GetOutputAsync(); + } + } + + private void SetModel(IRazorTemplatePage razorTemplatePage, object model = null) + { + if (razorTemplatePage is IRazorTemplatePage razorTemplatePageWithModel) + { + razorTemplatePageWithModel.Model = (TModel)model; + } + } + + private IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition) + { + if (templateDefinition.LocalizationResource != null) + { + return _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + } + + return _stringLocalizerFactory.CreateDefaultOrNull(); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo.Abp.TextTemplating.Scriban.csproj b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo.Abp.TextTemplating.Scriban.csproj new file mode 100644 index 0000000000..b4b0d66789 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo.Abp.TextTemplating.Scriban.csproj @@ -0,0 +1,19 @@ + + + + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/AbpTextTemplatingScribanModule.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/AbpTextTemplatingScribanModule.cs new file mode 100644 index 0000000000..789b9e376f --- /dev/null +++ b/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 + { + + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateLocalizer.cs similarity index 91% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs rename to framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateLocalizer.cs index 4e4a1e4833..e280cd194a 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateLocalizer.cs +++ b/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; } diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer.cs similarity index 94% rename from framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs rename to framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer.cs index 1d89dc3b68..3fdd667a82 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/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); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj index 506b086a88..3af0ba984d 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -5,22 +5,11 @@ netstandard2.0 - Volo.Abp.TextTemplating - Volo.Abp.TextTemplating - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - false - false - false - - - - - - + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs index 8d76391829..d5817d0d33 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs +++ b/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(); - var contentContributors = new List(); - - 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(options => - { - options.DefinitionProviders.AddIfNotContains(definitionProviders); - options.ContentContributors.AddIfNotContains(contentContributors); - }); - } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo.Abp.TextTemplating.Razor.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo.Abp.TextTemplating.Razor.Tests.csproj new file mode 100644 index 0000000000..ca26b60a4b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo.Abp.TextTemplating.Razor.Tests.csproj @@ -0,0 +1,23 @@ + + + + + + net5.0 + + + + + + Always + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs new file mode 100644 index 0000000000..9f4cd3ecaa --- /dev/null +++ b/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 + { + private readonly IAbpCompiledViewProvider _compiledViewProvider; + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public AbpCompiledViewProviderOptions_Tests() + { + _templateDefinitionManager = GetRequiredService(); + _compiledViewProvider = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.TemplateReferences.Add(RazorTestTemplates.TestTemplate, new List() + { + 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"); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorLocalizedTemplateContentReaderFactory_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorLocalizedTemplateContentReaderFactory_Tests.cs new file mode 100644 index 0000000000..53eaf0fc80 --- /dev/null +++ b/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 + { + 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" + + Environment.NewLine + + "Welcome @Model.Name to the abp.io!"; + + WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase" + + Environment.NewLine + + "Merhaba @Model.Name, abp.io'ya hoşgeldiniz!"; + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateDefinitionTests.cs new file mode 100644 index 0000000000..53c40b5ea8 --- /dev/null +++ b/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 + { + + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderer_Tests.cs new file mode 100644 index 0000000000..07da572c1e --- /dev/null +++ b/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 + { + private readonly ITemplateRenderer _templateRenderer; + + public RazorTemplateRenderer_Tests() + { + _templateRenderer = GetRequiredService(); + } + + [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; + } + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..3bb1d7f831 --- /dev/null +++ b/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)); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTextTemplatingTestModule.cs new file mode 100644 index 0000000000..df2f8b3b3d --- /dev/null +++ b/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(options => + { + options.FileSets.AddEmbedded("Volo.Abp.TextTemplating.Razor"); + }); + + Configure(options => + { + options.References.Add(MetadataReference.CreateFromFile(typeof(RazorTextTemplatingTestModule).Assembly.Location)); + }); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorVirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorVirtualFileTemplateContributor_Tests.cs new file mode 100644 index 0000000000..5634ccfa2c --- /dev/null +++ b/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 + { + public RazorVirtualFileTemplateContributor_Tests() + { + WelcomeEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase" + + Environment.NewLine + + "Welcome @Model.Name to the abp.io!"; + + WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase" + + Environment.NewLine + + "Merhaba @Model.Name, abp.io'ya hoşgeldiniz!"; + + ForgotPasswordEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase" + + Environment.NewLine + + "@Localizer[\"HelloText\", Model.Name], @Localizer[\"HowAreYou\"]. Please click to the following link to get an email to reset your password!"; + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ForgotPasswordEmail.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ForgotPasswordEmail.cshtml new file mode 100644 index 0000000000..e02bc728ab --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ForgotPasswordEmail.cshtml @@ -0,0 +1,2 @@ +@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase +@Localizer["HelloText", Model.Name], @Localizer["HowAreYou"]. Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs new file mode 100644 index 0000000000..3fa87f7d90 --- /dev/null +++ b/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"; + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ShowDecimalNumber.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ShowDecimalNumber.cshtml new file mode 100644 index 0000000000..d309dc5a2b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/ShowDecimalNumber.cshtml @@ -0,0 +1,2 @@ +@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase +@Model.Amount \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplate.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplate.cshtml new file mode 100644 index 0000000000..3de93e1e40 --- /dev/null +++ b/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(); +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplateLayout1.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/TestTemplateLayout1.cshtml new file mode 100644 index 0000000000..4d3bc7b7c2 --- /dev/null +++ b/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* \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/en.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/en.cshtml new file mode 100644 index 0000000000..410bfe1240 --- /dev/null +++ b/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 +Welcome @Model.Name to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/tr.cshtml b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/WelcomeEmail/tr.cshtml new file mode 100644 index 0000000000..7ea3c8802a --- /dev/null +++ b/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 +Merhaba @Model.Name, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo.Abp.TextTemplating.Scriban.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo.Abp.TextTemplating.Scriban.Tests.csproj new file mode 100644 index 0000000000..d17a4d46fa --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo.Abp.TextTemplating.Scriban.Tests.csproj @@ -0,0 +1,23 @@ + + + + + + net5.0 + + + + + + Always + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ForgotPasswordEmail.tpl similarity index 100% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ForgotPasswordEmail.tpl diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ShowDecimalNumber.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ShowDecimalNumber.tpl similarity index 100% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ShowDecimalNumber.tpl rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ShowDecimalNumber.tpl diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/TestTemplateLayout1.tpl similarity index 100% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/TestTemplateLayout1.tpl diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/en.tpl similarity index 100% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/en.tpl diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/tr.tpl similarity index 100% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/WelcomeEmail/tr.tpl diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanLocalizedTemplateContentReaderFactory_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanLocalizedTemplateContentReaderFactory_Tests.cs new file mode 100644 index 0000000000..1757bdaa17 --- /dev/null +++ b/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 + { + 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!"; + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateDefinitionTests.cs new file mode 100644 index 0000000000..e454af36ef --- /dev/null +++ b/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 + { + + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer_Tests.cs similarity index 95% rename from framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs rename to framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderer_Tests.cs index ca4ae3e3fd..a076301919 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/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 { private readonly ITemplateRenderer _templateRenderer; - public TemplateRenderer_Tests() + public ScribanTemplateRenderer_Tests() { _templateRenderer = GetRequiredService(); } @@ -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() { diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..956c376fd0 --- /dev/null +++ b/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); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTextTemplatingTestModule.cs new file mode 100644 index 0000000000..2ed2e51b56 --- /dev/null +++ b/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(options => + { + options.FileSets.AddEmbedded("Volo.Abp.TextTemplating.Scriban"); + }); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanVirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanVirtualFileTemplateContributor_Tests.cs new file mode 100644 index 0000000000..026b8abe59 --- /dev/null +++ b/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 + { + 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!"; + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 555c2934b0..65983605be 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -10,15 +10,12 @@ - - Always - - + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs index 294596c693..591f6c9caf 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs +++ b/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 { private readonly AbpTextTemplatingOptions _options; @@ -21,4 +21,4 @@ namespace Volo.Abp.TextTemplating .ShouldContain(typeof(TestTemplateDefinitionProvider)); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs index ca5dc20445..24f8bad395 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs +++ b/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 + public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest + where TStartupModule : IAbpModule { protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 8e8b69e116..74174ea690 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/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) diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index 51c78cc13e..6fd97cd2d4 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/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 : AbpTextTemplatingTestBase + where TStartupModule : IAbpModule { - private readonly ITemplateDefinitionManager _templateDefinitionManager; + protected readonly ITemplateDefinitionManager TemplateDefinitionManager; - public TemplateDefinitionTests() + protected TemplateDefinitionTests() { - _templateDefinitionManager = GetRequiredService(); + TemplateDefinitionManager = GetRequiredService(); } [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); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 9db711d70c..289bf1b04f 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/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) + ) ); } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs index 63e526ac1d..7a56997031 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs +++ b/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 : AbpTextTemplatingTestBase + 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(); + TemplateDefinitionManager = GetRequiredService(); } [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; diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index e5c7138118..4862e61203 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/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 : AbpTextTemplatingTestBase + 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(); - _virtualFileTemplateContentContributor = GetRequiredService(); + TemplateDefinitionManager = GetRequiredService(); + VirtualFileTemplateContentContributor = GetRequiredService(); } [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); } } } diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index c0e82e3486..e4ab829402 100644 --- a/nupkg/common.ps1 +++ b/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",