mirror of https://github.com/abpframework/abp.git
committed by
GitHub
93 changed files with 1521 additions and 969 deletions
@ -1,18 +0,0 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class AbpEmailTemplateOptions |
|||
{ |
|||
public string DefaultLayout { get; set; } |
|||
|
|||
public ITypeList<IEmailTemplateDefinitionProvider> DefinitionProviders { get; } |
|||
|
|||
public AbpEmailTemplateOptions() |
|||
{ |
|||
DefaultLayout = StandardEmailTemplates.DefaultLayout; |
|||
|
|||
DefinitionProviders = new TypeList<IEmailTemplateDefinitionProvider>(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,16 +1,26 @@ |
|||
using Volo.Abp.Emailing.Templates.VirtualFiles; |
|||
using Volo.Abp.TextTemplating; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class DefaultEmailTemplateProvider : EmailTemplateDefinitionProvider |
|||
public class DefaultEmailTemplateProvider : TemplateDefinitionProvider |
|||
{ |
|||
public override void Define(IEmailTemplateDefinitionContext context) |
|||
public override void Define(ITemplateDefinitionContext context) |
|||
{ |
|||
context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, defaultCultureName: "en", isLayout: true, layout: null) |
|||
.AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout")); |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
StandardEmailTemplates.Layout, |
|||
defaultCultureName: "en", |
|||
isLayout: true |
|||
).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout") |
|||
); |
|||
|
|||
context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage, defaultCultureName: "en") |
|||
.AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message")); |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
StandardEmailTemplates.Message, |
|||
defaultCultureName: "en", |
|||
layout: StandardEmailTemplates.Layout |
|||
).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message") |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +0,0 @@ |
|||
{{message}} |
|||
@ -1,42 +0,0 @@ |
|||
using System.Text; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplate |
|||
{ |
|||
public EmailTemplateDefinition Definition { get; } |
|||
|
|||
public string Content => ContentBuilder.ToString(); |
|||
|
|||
protected StringBuilder ContentBuilder { get; set; } |
|||
|
|||
public EmailTemplate(string content, EmailTemplateDefinition definition) |
|||
{ |
|||
ContentBuilder = new StringBuilder(content); |
|||
Definition = definition; |
|||
} |
|||
|
|||
public virtual void SetLayout(EmailTemplate layoutTemplate) |
|||
{ |
|||
if (!layoutTemplate.Definition.IsLayout) |
|||
{ |
|||
throw new AbpException($"Given template is not a layout template: {layoutTemplate.Definition.Name}"); |
|||
} |
|||
|
|||
var newStrBuilder = new StringBuilder(layoutTemplate.Content); |
|||
newStrBuilder.Replace("{{#content}}", ContentBuilder.ToString()); |
|||
|
|||
ContentBuilder = newStrBuilder; |
|||
} |
|||
|
|||
public virtual void SetContent(string content) |
|||
{ |
|||
ContentBuilder = new StringBuilder(content); |
|||
} |
|||
|
|||
public virtual void Replace(string name, string value) |
|||
{ |
|||
ContentBuilder.Replace("{{" + name + "}}", value); |
|||
} |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateContributorList : List<IEmailTemplateContributor> |
|||
{ |
|||
public string GetOrNull(string cultureName) |
|||
{ |
|||
foreach (var contributor in this.AsQueryable().Reverse()) |
|||
{ |
|||
var templateString = contributor.GetOrNull(cultureName); |
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
using System; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateDefinition |
|||
{ |
|||
public const string DefaultLayoutPlaceHolder = "_"; |
|||
|
|||
public string Name { get; } |
|||
|
|||
public bool IsLayout { get; } |
|||
|
|||
public string Layout { get; set; } |
|||
|
|||
public Type LocalizationResource { get; set; } |
|||
|
|||
public EmailTemplateContributorList Contributors { get; } |
|||
|
|||
public string DefaultCultureName { get; } |
|||
|
|||
public bool SingleTemplateFile { get; } |
|||
|
|||
public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, |
|||
string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) |
|||
{ |
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
LocalizationResource = localizationResource; |
|||
Contributors = new EmailTemplateContributorList(); |
|||
IsLayout = isLayout; |
|||
Layout = layout; |
|||
DefaultCultureName = defaultCultureName; |
|||
SingleTemplateFile = singleTemplateFile; |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateDefinitionContext : IEmailTemplateDefinitionContext |
|||
{ |
|||
protected Dictionary<string, EmailTemplateDefinition> EmailTemplates { get; } |
|||
|
|||
public EmailTemplateDefinitionContext(Dictionary<string, EmailTemplateDefinition> emailTemplates) |
|||
{ |
|||
EmailTemplates = emailTemplates; |
|||
} |
|||
|
|||
public virtual EmailTemplateDefinition GetOrNull(string name) |
|||
{ |
|||
return EmailTemplates.GetOrDefault(name); |
|||
} |
|||
|
|||
public virtual IReadOnlyList<EmailTemplateDefinition> GetAll() |
|||
{ |
|||
return EmailTemplates.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual void Add(params EmailTemplateDefinition[] definitions) |
|||
{ |
|||
if (definitions.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var definition in definitions) |
|||
{ |
|||
EmailTemplates[definition.Name] = definition; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateDefinitionDictionary : Dictionary<string, EmailTemplateDefinition> |
|||
{ |
|||
public EmailTemplateDefinitionDictionary Add(EmailTemplateDefinition emailTemplateDefinition) |
|||
{ |
|||
if (ContainsKey(emailTemplateDefinition.Name)) |
|||
{ |
|||
throw new AbpException( |
|||
"There is already an email template definition with given name: " + |
|||
emailTemplateDefinition.Name |
|||
); |
|||
} |
|||
|
|||
this[emailTemplateDefinition.Name] = emailTemplateDefinition; |
|||
|
|||
return this; |
|||
} |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateDefinitionManager : IEmailTemplateDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected Lazy<IDictionary<string, EmailTemplateDefinition>> EmailTemplateDefinitions { get; } |
|||
|
|||
protected AbpEmailTemplateOptions Options { get; } |
|||
|
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
public EmailTemplateDefinitionManager( |
|||
IOptions<AbpEmailTemplateOptions> options, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
Options = options.Value; |
|||
|
|||
EmailTemplateDefinitions = |
|||
new Lazy<IDictionary<string, EmailTemplateDefinition>>(CreateEmailTemplateDefinitions, true); |
|||
} |
|||
|
|||
public virtual EmailTemplateDefinition Get(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var template = GetOrNull(name); |
|||
|
|||
if (template == null) |
|||
{ |
|||
throw new AbpException("Undefined template: " + name); |
|||
} |
|||
|
|||
return template; |
|||
} |
|||
|
|||
public virtual IReadOnlyList<EmailTemplateDefinition> GetAll() |
|||
{ |
|||
return EmailTemplateDefinitions.Value.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual EmailTemplateDefinition GetOrNull(string name) |
|||
{ |
|||
return EmailTemplateDefinitions.Value.GetOrDefault(name); |
|||
} |
|||
|
|||
protected virtual IDictionary<string, EmailTemplateDefinition> CreateEmailTemplateDefinitions() |
|||
{ |
|||
var templates = new Dictionary<string, EmailTemplateDefinition>(); |
|||
|
|||
using (var scope = ServiceProvider.CreateScope()) |
|||
{ |
|||
var providers = Options |
|||
.DefinitionProviders |
|||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IEmailTemplateDefinitionProvider) |
|||
.ToList(); |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.Define(new EmailTemplateDefinitionContext(templates)); |
|||
} |
|||
} |
|||
|
|||
return templates; |
|||
} |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public abstract class EmailTemplateDefinitionProvider : IEmailTemplateDefinitionProvider, ITransientDependency |
|||
{ |
|||
public abstract void Define(IEmailTemplateDefinitionContext context); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateInitializationContext |
|||
{ |
|||
public EmailTemplateDefinition EmailTemplateDefinition { get; } |
|||
|
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
EmailTemplateDefinition = emailTemplateDefinition; |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
} |
|||
} |
|||
@ -1,121 +0,0 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Localization; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency |
|||
{ |
|||
protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager; |
|||
protected ITemplateLocalizer TemplateLocalizer { get; } |
|||
protected AbpEmailTemplateOptions Options { get; } |
|||
protected IStringLocalizerFactory StringLocalizerFactory; |
|||
|
|||
public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager, |
|||
ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory, |
|||
IOptions<AbpEmailTemplateOptions> options) |
|||
{ |
|||
EmailTemplateDefinitionManager = emailTemplateDefinitionManager; |
|||
TemplateLocalizer = templateLocalizer; |
|||
StringLocalizerFactory = stringLocalizerFactory; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public async Task<EmailTemplate> GetAsync(string name) |
|||
{ |
|||
return await GetAsync(name, CultureInfo.CurrentUICulture.Name); |
|||
} |
|||
|
|||
public async Task<EmailTemplate> GetAsync(string name, string cultureName) |
|||
{ |
|||
return await GetInternalAsync(name, cultureName); |
|||
} |
|||
|
|||
protected virtual async Task<EmailTemplate> GetInternalAsync(string name, string cultureName) |
|||
{ |
|||
var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name); |
|||
if (emailTemplateDefinition == null) |
|||
{ |
|||
// TODO: Localized message
|
|||
throw new AbpException($"email template {name} not definition"); |
|||
} |
|||
|
|||
var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName); |
|||
if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null) |
|||
{ |
|||
emailTemplateString = |
|||
emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName); |
|||
if (emailTemplateString != null) |
|||
{ |
|||
cultureName = emailTemplateDefinition.DefaultCultureName; |
|||
} |
|||
} |
|||
|
|||
if (emailTemplateString != null) |
|||
{ |
|||
var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition); |
|||
|
|||
await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName); |
|||
|
|||
if (emailTemplateDefinition.SingleTemplateFile) |
|||
{ |
|||
await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName); |
|||
} |
|||
|
|||
return emailTemplate; |
|||
} |
|||
|
|||
// TODO: Localized message
|
|||
throw new AbpException($"{cultureName} template not exist!"); |
|||
} |
|||
|
|||
protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition, |
|||
EmailTemplate emailTemplate, string cultureName) |
|||
{ |
|||
var layout = emailTemplateDefinition.Layout; |
|||
if (layout.IsNullOrWhiteSpace()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder) |
|||
{ |
|||
layout = Options.DefaultLayout; |
|||
} |
|||
|
|||
var layoutTemplate = await GetInternalAsync(layout, cultureName); |
|||
|
|||
emailTemplate.SetLayout(layoutTemplate); |
|||
} |
|||
|
|||
protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition, |
|||
EmailTemplate emailTemplate, string cultureName) |
|||
{ |
|||
if (emailTemplateDefinition.LocalizationResource == null) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource); |
|||
if (cultureName != null) |
|||
{ |
|||
using (CultureHelper.Use(new CultureInfo(cultureName))) |
|||
{ |
|||
emailTemplate.SetContent(TemplateLocalizer.Localize(localizer, emailTemplate.Content)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
emailTemplate.SetContent( |
|||
TemplateLocalizer.Localize(localizer, emailTemplate.Content) |
|||
); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface IEmailTemplateContributor |
|||
{ |
|||
void Initialize(EmailTemplateInitializationContext context); |
|||
|
|||
string GetOrNull(string cultureName); |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface IEmailTemplateDefinitionContext |
|||
{ |
|||
EmailTemplateDefinition GetOrNull(string name); |
|||
|
|||
void Add(params EmailTemplateDefinition[] definitions); |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface IEmailTemplateDefinitionManager |
|||
{ |
|||
[NotNull] |
|||
EmailTemplateDefinition Get([NotNull] string name); |
|||
|
|||
IReadOnlyList<EmailTemplateDefinition> GetAll(); |
|||
|
|||
EmailTemplateDefinition GetOrNull(string name); |
|||
} |
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface IEmailTemplateDefinitionProvider |
|||
{ |
|||
void Define(IEmailTemplateDefinitionContext context); |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface IEmailTemplateProvider |
|||
{ |
|||
Task<EmailTemplate> GetAsync(string name); |
|||
|
|||
Task<EmailTemplate> GetAsync(string name, string cultureName); |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public interface ITemplateRender |
|||
{ |
|||
Task<string> RenderAsync(string template, object model = null); |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
{{model.message}} |
|||
@ -1,15 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
using Scriban; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates |
|||
{ |
|||
public class TemplateRender : ITemplateRender, ITransientDependency |
|||
{ |
|||
public async Task<string> RenderAsync(string template, object model = null) |
|||
{ |
|||
var scribanTemplate = Template.Parse(template); |
|||
return await scribanTemplate.RenderAsync(model); |
|||
} |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
namespace Volo.Abp.Emailing.Templates.VirtualFiles |
|||
{ |
|||
public static class EmailTemplateDefinitionExtensions |
|||
{ |
|||
public static EmailTemplateDefinition AddTemplateVirtualFile( |
|||
this EmailTemplateDefinition emailTemplateDefinition, string path) |
|||
{ |
|||
emailTemplateDefinition.Contributors.Add(new SingleVirtualFileEmailTemplateContributor(path)); |
|||
return emailTemplateDefinition; |
|||
} |
|||
|
|||
public static EmailTemplateDefinition AddTemplateVirtualFiles( |
|||
this EmailTemplateDefinition emailTemplateDefinition, string path) |
|||
{ |
|||
emailTemplateDefinition.Contributors.Add(new MultipleVirtualFilesEmailTemplateContributor(path)); |
|||
return emailTemplateDefinition; |
|||
} |
|||
} |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates.VirtualFiles |
|||
{ |
|||
public class MultipleVirtualFilesEmailTemplateContributor : IEmailTemplateContributor |
|||
{ |
|||
private readonly string _virtualPath; |
|||
|
|||
private IVirtualFileProvider _virtualFileProvider; |
|||
|
|||
private Dictionary<string, string> _templateDictionary; |
|||
|
|||
private readonly object _syncObj = new object(); |
|||
|
|||
public MultipleVirtualFilesEmailTemplateContributor(string virtualPath) |
|||
{ |
|||
_virtualPath = virtualPath; |
|||
} |
|||
|
|||
public void Initialize(EmailTemplateInitializationContext context) |
|||
{ |
|||
_virtualFileProvider = context.ServiceProvider.GetRequiredService<IVirtualFileProvider>(); |
|||
} |
|||
|
|||
public string GetOrNull(string cultureName) |
|||
{ |
|||
return GetTemplateDictionary().GetOrDefault(cultureName); |
|||
} |
|||
|
|||
private Dictionary<string, string> GetTemplateDictionary() |
|||
{ |
|||
var dictionaries = _templateDictionary; |
|||
if (dictionaries != null) |
|||
{ |
|||
return dictionaries; |
|||
} |
|||
|
|||
lock (_syncObj) |
|||
{ |
|||
dictionaries = _templateDictionary; |
|||
if (dictionaries != null) |
|||
{ |
|||
return dictionaries; |
|||
} |
|||
|
|||
_templateDictionary = new Dictionary<string, string>(); |
|||
foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) |
|||
{ |
|||
if (file.IsDirectory) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// TODO: How to normalize file names?
|
|||
_templateDictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); |
|||
} |
|||
|
|||
dictionaries = _templateDictionary; |
|||
} |
|||
|
|||
return dictionaries; |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.Emailing.Templates.VirtualFiles |
|||
{ |
|||
public class SingleVirtualFileEmailTemplateContributor : IEmailTemplateContributor |
|||
{ |
|||
private readonly string _virtualPath; |
|||
|
|||
private IVirtualFileProvider _virtualFileProvider; |
|||
|
|||
public SingleVirtualFileEmailTemplateContributor(string virtualPath) |
|||
{ |
|||
_virtualPath = virtualPath; |
|||
} |
|||
|
|||
public void Initialize(EmailTemplateInitializationContext context) |
|||
{ |
|||
_virtualFileProvider = context.ServiceProvider.GetRequiredService<IVirtualFileProvider>(); |
|||
} |
|||
|
|||
public string GetOrNull(string cultureName) |
|||
{ |
|||
var file = _virtualFileProvider.GetFileInfo(_virtualPath); |
|||
if (file == null || !file.Exists || file.IsDirectory) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return file.ReadAsString(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
using System.Text.RegularExpressions; |
|||
using Microsoft.Extensions.Localization; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Localization |
|||
{ |
|||
public class TemplateLocalizer : ITemplateLocalizer, ITransientDependency |
|||
{ |
|||
public string Localize(IStringLocalizer localizer, string text) |
|||
{ |
|||
return new Regex("\\{\\{#L:.+?\\}\\}") |
|||
.Replace( |
|||
text, |
|||
match => localizer[match.Value.Substring(5, match.Length - 7)] |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.TextTemplating</AssemblyName> |
|||
<PackageId>Volo.Abp.TextTemplating</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Scriban" Version="2.1.1" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -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 AbpTextTemplatingModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
AutoAddProvidersAndContributors(context.Services); |
|||
} |
|||
|
|||
private static void AutoAddProvidersAndContributors(IServiceCollection services) |
|||
{ |
|||
var definitionProviders = new List<Type>(); |
|||
var contentContributors = new List<Type>(); |
|||
|
|||
services.OnRegistred(context => |
|||
{ |
|||
if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
definitionProviders.Add(context.ImplementationType); |
|||
} |
|||
|
|||
if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
contentContributors.Add(context.ImplementationType); |
|||
} |
|||
}); |
|||
|
|||
services.Configure<AbpTextTemplatingOptions>(options => |
|||
{ |
|||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
|||
options.ContentContributors.AddIfNotContains(contentContributors); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class AbpTextTemplatingOptions |
|||
{ |
|||
public ITypeList<ITemplateDefinitionProvider> DefinitionProviders { get; } |
|||
public ITypeList<ITemplateContentContributor> ContentContributors { get; } |
|||
|
|||
public AbpTextTemplatingOptions() |
|||
{ |
|||
DefinitionProviders = new TypeList<ITemplateDefinitionProvider>(); |
|||
ContentContributors = new TypeList<ITemplateContentContributor>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateContentContributor |
|||
{ |
|||
Task<string> GetOrNullAsync(TemplateContentContributorContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateContentProvider |
|||
{ |
|||
Task<string> GetContentOrNullAsync( |
|||
[NotNull] string templateName, |
|||
[CanBeNull] string cultureName = null, |
|||
bool tryDefaults = true, |
|||
bool useCurrentCultureIfCultureNameIsNull = true |
|||
); |
|||
|
|||
Task<string> GetContentOrNullAsync( |
|||
[NotNull] TemplateDefinition templateDefinition, |
|||
[CanBeNull] string cultureName = null, |
|||
bool tryDefaults = true, |
|||
bool useCurrentCultureIfCultureNameIsNull = true |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateDefinitionContext |
|||
{ |
|||
IReadOnlyList<TemplateDefinition> GetAll(string name); |
|||
|
|||
TemplateDefinition GetOrNull(string name); |
|||
|
|||
void Add(params TemplateDefinition[] definitions); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateDefinitionManager |
|||
{ |
|||
[NotNull] |
|||
TemplateDefinition Get([NotNull] string name); |
|||
|
|||
[NotNull] |
|||
IReadOnlyList<TemplateDefinition> GetAll(); |
|||
|
|||
[CanBeNull] |
|||
TemplateDefinition GetOrNull(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateDefinitionProvider |
|||
{ |
|||
void PreDefine(ITemplateDefinitionContext context); |
|||
|
|||
void Define(ITemplateDefinitionContext context); |
|||
|
|||
void PostDefine(ITemplateDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public interface ITemplateRenderer |
|||
{ |
|||
/// <summary>
|
|||
/// Renders a text template.
|
|||
/// </summary>
|
|||
/// <param name="templateName">The template name</param>
|
|||
/// <param name="model">An optional model object that is used in the template</param>
|
|||
/// <param name="cultureName">Culture name. Uses the <see cref="CultureInfo.CurrentUICulture"/> if not specified</param>
|
|||
/// <param name="globalContext">A dictionary which can be used to import global objects to the template</param>
|
|||
/// <returns></returns>
|
|||
Task<string> RenderAsync( |
|||
[NotNull] string templateName, |
|||
[CanBeNull] object model = null, |
|||
[CanBeNull] string cultureName = null, |
|||
[CanBeNull] Dictionary<string, object> globalContext = null |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateContentContributorContext |
|||
{ |
|||
[NotNull] |
|||
public TemplateDefinition TemplateDefinition { get; } |
|||
|
|||
[NotNull] |
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
[CanBeNull] |
|||
public string Culture { get; } |
|||
|
|||
public TemplateContentContributorContext( |
|||
[NotNull] TemplateDefinition templateDefinition, |
|||
[NotNull] IServiceProvider serviceProvider, |
|||
[CanBeNull] string culture) |
|||
{ |
|||
TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); |
|||
ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); |
|||
Culture = culture; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,170 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency |
|||
{ |
|||
public IHybridServiceScopeFactory ServiceScopeFactory { get; } |
|||
public AbpTextTemplatingOptions Options { get; } |
|||
private readonly ITemplateDefinitionManager _templateDefinitionManager; |
|||
|
|||
public TemplateContentProvider( |
|||
ITemplateDefinitionManager templateDefinitionManager, |
|||
IHybridServiceScopeFactory serviceScopeFactory, |
|||
IOptions<AbpTextTemplatingOptions> options) |
|||
{ |
|||
ServiceScopeFactory = serviceScopeFactory; |
|||
Options = options.Value; |
|||
_templateDefinitionManager = templateDefinitionManager; |
|||
} |
|||
|
|||
public virtual Task<string> GetContentOrNullAsync( |
|||
[NotNull] string templateName, |
|||
[CanBeNull] string cultureName = null, |
|||
bool tryDefaults = true, |
|||
bool useCurrentCultureIfCultureNameIsNull = true) |
|||
{ |
|||
var template = _templateDefinitionManager.Get(templateName); |
|||
return GetContentOrNullAsync(template, cultureName); |
|||
} |
|||
|
|||
public virtual async Task<string> GetContentOrNullAsync( |
|||
[NotNull] TemplateDefinition templateDefinition, |
|||
[CanBeNull] string cultureName = null, |
|||
bool tryDefaults = true, |
|||
bool useCurrentCultureIfCultureNameIsNull = true) |
|||
{ |
|||
Check.NotNull(templateDefinition, nameof(templateDefinition)); |
|||
|
|||
if (!Options.ContentContributors.Any()) |
|||
{ |
|||
throw new AbpException( |
|||
$"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" |
|||
); |
|||
} |
|||
|
|||
using (var scope = ServiceScopeFactory.CreateScope()) |
|||
{ |
|||
string templateString = null; |
|||
|
|||
if (cultureName == null && useCurrentCultureIfCultureNameIsNull) |
|||
{ |
|||
cultureName = CultureInfo.CurrentUICulture.Name; |
|||
} |
|||
|
|||
var contributors = CreateTemplateContentContributors(scope.ServiceProvider); |
|||
|
|||
//Try to get from the requested culture
|
|||
templateString = await GetContentOrNullAsync( |
|||
contributors, |
|||
new TemplateContentContributorContext( |
|||
templateDefinition, |
|||
scope.ServiceProvider, |
|||
cultureName |
|||
) |
|||
); |
|||
|
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
|
|||
if (!tryDefaults) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
//Try to get from same culture without country code
|
|||
if (cultureName != null && cultureName.Contains("-")) //Example: "tr-TR"
|
|||
{ |
|||
templateString = await GetContentOrNullAsync( |
|||
contributors, |
|||
new TemplateContentContributorContext( |
|||
templateDefinition, |
|||
scope.ServiceProvider, |
|||
CultureHelper.GetBaseCultureName(cultureName) |
|||
) |
|||
); |
|||
|
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
} |
|||
|
|||
if (templateDefinition.IsInlineLocalized) |
|||
{ |
|||
//Try to get culture independent content
|
|||
templateString = await GetContentOrNullAsync( |
|||
contributors, |
|||
new TemplateContentContributorContext( |
|||
templateDefinition, |
|||
scope.ServiceProvider, |
|||
null |
|||
) |
|||
); |
|||
|
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
//Try to get from default culture
|
|||
if (templateDefinition.DefaultCultureName != null) |
|||
{ |
|||
templateString = await GetContentOrNullAsync( |
|||
contributors, |
|||
new TemplateContentContributorContext( |
|||
templateDefinition, |
|||
scope.ServiceProvider, |
|||
templateDefinition.DefaultCultureName |
|||
) |
|||
); |
|||
|
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
//Not found
|
|||
return null; |
|||
} |
|||
|
|||
protected virtual ITemplateContentContributor[] CreateTemplateContentContributors(IServiceProvider serviceProvider) |
|||
{ |
|||
return Options.ContentContributors |
|||
.Select(type => (ITemplateContentContributor)serviceProvider.GetRequiredService(type)) |
|||
.Reverse() |
|||
.ToArray(); |
|||
} |
|||
|
|||
protected virtual async Task<string> GetContentOrNullAsync( |
|||
ITemplateContentContributor[] contributors, |
|||
TemplateContentContributorContext context) |
|||
{ |
|||
foreach (var contributor in contributors) |
|||
{ |
|||
var templateString = await contributor.GetOrNullAsync(context); |
|||
if (templateString != null) |
|||
{ |
|||
return templateString; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateDefinition |
|||
{ |
|||
public const int MaxNameLength = 128; |
|||
|
|||
[NotNull] |
|||
public string Name { get; } |
|||
|
|||
[NotNull] |
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get => _displayName; |
|||
set |
|||
{ |
|||
Check.NotNull(value, nameof(value)); |
|||
_displayName = value; |
|||
} |
|||
} |
|||
private ILocalizableString _displayName; |
|||
|
|||
public bool IsLayout { get; } |
|||
|
|||
[CanBeNull] |
|||
public string Layout { get; set; } |
|||
|
|||
[CanBeNull] |
|||
public Type LocalizationResource { get; set; } |
|||
|
|||
public bool IsInlineLocalized { get; set; } |
|||
|
|||
[CanBeNull] |
|||
public string DefaultCultureName { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets/sets a key-value on the <see cref="Properties"/>.
|
|||
/// </summary>
|
|||
/// <param name="name">Name of the property</param>
|
|||
/// <returns>
|
|||
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
|
|||
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
|
|||
/// </returns>
|
|||
[CanBeNull] |
|||
public object this[string name] |
|||
{ |
|||
get => Properties.GetOrDefault(name); |
|||
set => Properties[name] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Can be used to get/set custom properties for this feature.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
public TemplateDefinition( |
|||
[NotNull] string name, |
|||
[CanBeNull] Type localizationResource = null, |
|||
[CanBeNull] ILocalizableString displayName = null, |
|||
bool isLayout = false, |
|||
string layout = null, |
|||
string defaultCultureName = null) |
|||
{ |
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); |
|||
LocalizationResource = localizationResource; |
|||
DisplayName = displayName ?? new FixedLocalizableString(Name); |
|||
IsLayout = isLayout; |
|||
Layout = layout; |
|||
DefaultCultureName = defaultCultureName; |
|||
Properties = new Dictionary<string, object>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual TemplateDefinition WithProperty(string key, object value) |
|||
{ |
|||
Properties[key] = value; |
|||
return this; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateDefinitionContext : ITemplateDefinitionContext |
|||
{ |
|||
protected Dictionary<string, TemplateDefinition> Templates { get; } |
|||
|
|||
public TemplateDefinitionContext(Dictionary<string, TemplateDefinition> templates) |
|||
{ |
|||
Templates = templates; |
|||
} |
|||
|
|||
public IReadOnlyList<TemplateDefinition> GetAll(string name) |
|||
{ |
|||
return Templates.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual TemplateDefinition GetOrNull(string name) |
|||
{ |
|||
return Templates.GetOrDefault(name); |
|||
} |
|||
|
|||
public virtual IReadOnlyList<TemplateDefinition> GetAll() |
|||
{ |
|||
return Templates.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual void Add(params TemplateDefinition[] definitions) |
|||
{ |
|||
if (definitions.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var definition in definitions) |
|||
{ |
|||
Templates[definition.Name] = definition; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.TextTemplating.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public static class TemplateDefinitionExtensions |
|||
{ |
|||
public static TemplateDefinition WithVirtualFilePath( |
|||
[NotNull] this TemplateDefinition templateDefinition, |
|||
[NotNull] string virtualPath) |
|||
{ |
|||
Check.NotNull(templateDefinition, nameof(templateDefinition)); |
|||
|
|||
return templateDefinition.WithProperty( |
|||
VirtualFileTemplateContentContributor.VirtualPathPropertyName, |
|||
virtualPath |
|||
); |
|||
} |
|||
|
|||
public static string GetVirtualFilePathOrNull( |
|||
[NotNull] this TemplateDefinition templateDefinition) |
|||
{ |
|||
Check.NotNull(templateDefinition, nameof(templateDefinition)); |
|||
|
|||
return templateDefinition |
|||
.Properties |
|||
.GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected Lazy<IDictionary<string, TemplateDefinition>> TemplateDefinitions { get; } |
|||
|
|||
protected AbpTextTemplatingOptions Options { get; } |
|||
|
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
public TemplateDefinitionManager( |
|||
IOptions<AbpTextTemplatingOptions> options, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
Options = options.Value; |
|||
|
|||
TemplateDefinitions = |
|||
new Lazy<IDictionary<string, TemplateDefinition>>(CreateTextTemplateDefinitions, true); |
|||
} |
|||
|
|||
public virtual TemplateDefinition Get(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var template = GetOrNull(name); |
|||
|
|||
if (template == null) |
|||
{ |
|||
throw new AbpException("Undefined template: " + name); |
|||
} |
|||
|
|||
return template; |
|||
} |
|||
|
|||
public virtual IReadOnlyList<TemplateDefinition> GetAll() |
|||
{ |
|||
return TemplateDefinitions.Value.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual TemplateDefinition GetOrNull(string name) |
|||
{ |
|||
return TemplateDefinitions.Value.GetOrDefault(name); |
|||
} |
|||
|
|||
protected virtual IDictionary<string, TemplateDefinition> CreateTextTemplateDefinitions() |
|||
{ |
|||
var templates = new Dictionary<string, TemplateDefinition>(); |
|||
|
|||
using (var scope = ServiceProvider.CreateScope()) |
|||
{ |
|||
var providers = Options |
|||
.DefinitionProviders |
|||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) |
|||
.ToList(); |
|||
|
|||
var context = new TemplateDefinitionContext(templates); |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.PreDefine(context); |
|||
} |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.Define(context); |
|||
} |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.PostDefine(context); |
|||
} |
|||
|
|||
SetIsInlineLocalized( |
|||
templates, |
|||
scope.ServiceProvider |
|||
); |
|||
} |
|||
|
|||
return templates; |
|||
} |
|||
|
|||
protected virtual void SetIsInlineLocalized( |
|||
Dictionary<string, TemplateDefinition> templates, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
var virtualFileProvider = serviceProvider.GetRequiredService<IVirtualFileProvider>(); |
|||
|
|||
foreach (var templateDefinition in templates.Values) |
|||
{ |
|||
var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); |
|||
if (virtualPath == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var fileInfo = virtualFileProvider.GetFileInfo(virtualPath); |
|||
if (!fileInfo.Exists) |
|||
{ |
|||
throw new AbpException("Could not find a file/folder at the location: " + virtualPath); |
|||
} |
|||
|
|||
templateDefinition.IsInlineLocalized = !fileInfo.IsDirectory; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency |
|||
{ |
|||
public virtual void PreDefine(ITemplateDefinitionContext context) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public abstract void Define(ITemplateDefinitionContext context); |
|||
|
|||
public virtual void PostDefine(ITemplateDefinitionContext context) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Localization; |
|||
using Scriban; |
|||
using Scriban.Runtime; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateRenderer : ITemplateRenderer, ITransientDependency |
|||
{ |
|||
private readonly ITemplateContentProvider _templateContentProvider; |
|||
private readonly ITemplateDefinitionManager _templateDefinitionManager; |
|||
private readonly IStringLocalizerFactory _stringLocalizerFactory; |
|||
|
|||
public TemplateRenderer( |
|||
ITemplateContentProvider templateContentProvider, |
|||
ITemplateDefinitionManager templateDefinitionManager, |
|||
IStringLocalizerFactory stringLocalizerFactory) |
|||
{ |
|||
_templateContentProvider = templateContentProvider; |
|||
_templateDefinitionManager = templateDefinitionManager; |
|||
_stringLocalizerFactory = stringLocalizerFactory; |
|||
} |
|||
|
|||
public virtual async Task<string> RenderAsync( |
|||
[NotNull] string templateName, |
|||
[CanBeNull] object model = null, |
|||
[CanBeNull] string cultureName = null, |
|||
[CanBeNull] Dictionary<string, object> globalContext = null) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); |
|||
|
|||
if (globalContext == null) |
|||
{ |
|||
globalContext = new Dictionary<string, object>(); |
|||
} |
|||
|
|||
if (cultureName == null) |
|||
{ |
|||
return await RenderInternalAsync( |
|||
templateName, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
else |
|||
{ |
|||
using (CultureHelper.Use(cultureName)) |
|||
{ |
|||
return await RenderInternalAsync( |
|||
templateName, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderInternalAsync( |
|||
string templateName, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var templateDefinition = _templateDefinitionManager.Get(templateName); |
|||
|
|||
var renderedContent = await RenderSingleTemplateAsync( |
|||
templateDefinition, |
|||
globalContext, |
|||
model |
|||
); |
|||
|
|||
if (templateDefinition.Layout != null) |
|||
{ |
|||
globalContext["content"] = renderedContent; |
|||
renderedContent = await RenderInternalAsync( |
|||
templateDefinition.Layout, |
|||
globalContext |
|||
); |
|||
} |
|||
|
|||
return renderedContent; |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderSingleTemplateAsync( |
|||
TemplateDefinition templateDefinition, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var rawTemplateContent = await _templateContentProvider |
|||
.GetContentOrNullAsync( |
|||
templateDefinition |
|||
); |
|||
|
|||
return await RenderTemplateContentWithScribanAsync( |
|||
templateDefinition, |
|||
rawTemplateContent, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderTemplateContentWithScribanAsync( |
|||
TemplateDefinition templateDefinition, |
|||
string templateContent, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var context = CreateScribanTemplateContext( |
|||
templateDefinition, |
|||
globalContext, |
|||
model |
|||
); |
|||
|
|||
return await Template |
|||
.Parse(templateContent) |
|||
.RenderAsync(context); |
|||
} |
|||
|
|||
protected virtual TemplateContext CreateScribanTemplateContext( |
|||
TemplateDefinition templateDefinition, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var context = new TemplateContext(); |
|||
|
|||
var scriptObject = new ScriptObject(); |
|||
|
|||
scriptObject.Import(globalContext); |
|||
|
|||
if (model != null) |
|||
{ |
|||
scriptObject["model"] = model; |
|||
} |
|||
|
|||
if (templateDefinition.LocalizationResource != null) |
|||
{ |
|||
var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); |
|||
scriptObject.Import( |
|||
"L", |
|||
new Func<string, string>( |
|||
name => localizer[name] |
|||
) |
|||
); |
|||
} |
|||
|
|||
context.PushGlobal(scriptObject); |
|||
|
|||
return context; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.FileProviders; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader |
|||
{ |
|||
private string _content; |
|||
|
|||
public async Task ReadContentsAsync(IFileInfo fileInfo) |
|||
{ |
|||
_content = await fileInfo.ReadAsStringAsync(); |
|||
} |
|||
|
|||
public string GetContentOrNull(string culture) |
|||
{ |
|||
if (culture == null) |
|||
{ |
|||
return _content; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public interface ILocalizedTemplateContentReader |
|||
{ |
|||
public string GetContentOrNull([CanBeNull] string culture); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public interface ILocalizedTemplateContentReaderFactory |
|||
{ |
|||
Task<ILocalizedTemplateContentReader> CreateAsync(TemplateDefinition templateDefinition); |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency |
|||
{ |
|||
private readonly IVirtualFileProvider _virtualFileProvider; |
|||
private readonly Dictionary<string, ILocalizedTemplateContentReader> _readerCache; |
|||
private readonly ReaderWriterLockSlim _lock; |
|||
|
|||
public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) |
|||
{ |
|||
_virtualFileProvider = virtualFileProvider; |
|||
_readerCache = new Dictionary<string, ILocalizedTemplateContentReader>(); |
|||
_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); |
|||
} |
|||
|
|||
public async Task<ILocalizedTemplateContentReader> CreateAsync(TemplateDefinition templateDefinition) |
|||
{ |
|||
_lock.EnterUpgradeableReadLock(); |
|||
|
|||
try |
|||
{ |
|||
var reader = _readerCache.GetOrDefault(templateDefinition.Name); |
|||
if (reader != null) |
|||
{ |
|||
return reader; |
|||
} |
|||
|
|||
_lock.EnterWriteLock(); |
|||
|
|||
try |
|||
{ |
|||
reader = await CreateInternalAsync(templateDefinition); |
|||
_readerCache[templateDefinition.Name] = reader; |
|||
return reader; |
|||
} |
|||
finally |
|||
{ |
|||
_lock.ExitWriteLock(); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
_lock.ExitUpgradeableReadLock(); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<ILocalizedTemplateContentReader> CreateInternalAsync( |
|||
TemplateDefinition templateDefinition) |
|||
{ |
|||
var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); |
|||
if (virtualPath == null) |
|||
{ |
|||
return NullLocalizedTemplateContentReader.Instance; |
|||
} |
|||
|
|||
var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); |
|||
if (!fileInfo.Exists) |
|||
{ |
|||
throw new AbpException("Could not find a file/folder at the location: " + virtualPath); |
|||
} |
|||
|
|||
if (fileInfo.IsDirectory) |
|||
{ |
|||
var folderReader = new VirtualFolderLocalizedTemplateContentReader(); |
|||
await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); |
|||
return folderReader; |
|||
} |
|||
else //File
|
|||
{ |
|||
var singleFileReader = new FileInfoLocalizedTemplateContentReader(); |
|||
await singleFileReader.ReadContentsAsync(fileInfo); |
|||
return singleFileReader; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class NullLocalizedTemplateContentReader : ILocalizedTemplateContentReader |
|||
{ |
|||
public static NullLocalizedTemplateContentReader Instance { get; } = new NullLocalizedTemplateContentReader(); |
|||
|
|||
private NullLocalizedTemplateContentReader() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public string GetContentOrNull(string culture) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class VirtualFileTemplateContentContributor : ITemplateContentContributor, ITransientDependency |
|||
{ |
|||
public const string VirtualPathPropertyName = "VirtualPath"; |
|||
|
|||
private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; |
|||
|
|||
public VirtualFileTemplateContentContributor( |
|||
ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) |
|||
{ |
|||
_localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; |
|||
} |
|||
|
|||
public virtual async Task<string> GetOrNullAsync(TemplateContentContributorContext context) |
|||
{ |
|||
var localizedReader = await _localizedTemplateContentReaderFactory |
|||
.CreateAsync(context.TemplateDefinition); |
|||
|
|||
return localizedReader.GetContentOrNull( |
|||
context.Culture |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader |
|||
{ |
|||
private Dictionary<string, string> _dictionary; |
|||
|
|||
public async Task ReadContentsAsync( |
|||
IVirtualFileProvider virtualFileProvider, |
|||
string virtualPath) |
|||
{ |
|||
_dictionary = new Dictionary<string, string>(); |
|||
|
|||
var directoryInfo = virtualFileProvider.GetFileInfo(virtualPath); |
|||
if (!directoryInfo.IsDirectory) |
|||
{ |
|||
throw new AbpException("Given virtual path is not a folder: " + virtualPath); |
|||
} |
|||
|
|||
foreach (var file in virtualFileProvider.GetDirectoryContents(virtualPath)) |
|||
{ |
|||
if (file.IsDirectory) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
_dictionary.Add(file.Name.RemovePostFix(".tpl"), await file.ReadAsStringAsync()); |
|||
} |
|||
} |
|||
|
|||
public string GetContentOrNull(string cultureName) |
|||
{ |
|||
if (cultureName == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return _dictionary.GetOrDefault(cultureName); |
|||
} |
|||
} |
|||
} |
|||
@ -1,65 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Emailing.Templates; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Emailing |
|||
{ |
|||
public class EmailTemplateRender_Tests : AbpIntegratedTest<AbpEmailingTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
private readonly ITemplateRender _templateRender; |
|||
|
|||
public EmailTemplateRender_Tests() |
|||
{ |
|||
_templateRender = GetRequiredService<ITemplateRender>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task RenderAsync() |
|||
{ |
|||
var template = "Hello {{email}} {{ for order in orders }}{{ order.id }}:{{ order.name }},{{ end }}"; |
|||
|
|||
var model = new ModelClass |
|||
{ |
|||
Email = "john@abp.io", |
|||
Orders = new List<ModelClass.Order> |
|||
{ |
|||
new ModelClass.Order |
|||
{ |
|||
Id = "1", |
|||
Name = "iphone" |
|||
}, |
|||
new ModelClass.Order |
|||
{ |
|||
Id = "2", |
|||
Name = "ipad" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var result = await _templateRender.RenderAsync(template, model); |
|||
result.ShouldBe("Hello john@abp.io 1:iphone,2:ipad,"); |
|||
} |
|||
|
|||
public class ModelClass |
|||
{ |
|||
public string Email { get; set; } |
|||
|
|||
public List<Order> Orders { get; set; } |
|||
|
|||
public class Order |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,54 +0,0 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Emailing.Templates; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Emailing |
|||
{ |
|||
public class EmailTemplateStore_Tests : AbpIntegratedTest<AbpEmailingTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
|
|||
private readonly IEmailTemplateProvider _emailTemplateProvider; |
|||
|
|||
public EmailTemplateStore_Tests() |
|||
{ |
|||
_emailTemplateProvider = GetRequiredService<IEmailTemplateProvider>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Registered_Template() |
|||
{ |
|||
var template = await _emailTemplateProvider.GetAsync("template1", "tr"); |
|||
template.Content.ShouldContain("Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın."); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Default_Culture_Template() |
|||
{ |
|||
var template = await _emailTemplateProvider.GetAsync("template1", "zh-Hans"); |
|||
template.Content.ShouldContain("Please confirm your email address by clicking the link below."); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Registered_Template_With_Layout() |
|||
{ |
|||
var template = await _emailTemplateProvider.GetAsync("template2", "en"); |
|||
|
|||
template.Content.ShouldContain($"<body>{Environment.NewLine} " + "Please confirm your email address by clicking the link below."); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Registered_Template_With_Localize() |
|||
{ |
|||
var template = await _emailTemplateProvider.GetAsync("template3", "tr"); |
|||
template.Content.ShouldContain("Merhaba Abp"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Volo.Abp.Emailing.Localization |
|||
{ |
|||
public class AbpEmailingTestResource |
|||
{ |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"culture": "cs", |
|||
"texts": { |
|||
"hello": "ahoj" |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"culture": "pl", |
|||
"texts": { |
|||
"hello": "witaj" |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"culture": "pt-BR", |
|||
"texts": { |
|||
"hello": "Olá" |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"culture": "vi", |
|||
"texts": { |
|||
"hello": "xin chào" |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"hello": "哈囉" |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
using Volo.Abp.Emailing.Localization; |
|||
using Volo.Abp.Emailing.Templates; |
|||
using Volo.Abp.Emailing.Templates.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.Emailing |
|||
{ |
|||
public class TestEmailTemplateProvider : EmailTemplateDefinitionProvider |
|||
{ |
|||
public override void Define(IEmailTemplateDefinitionContext context) |
|||
{ |
|||
var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null) |
|||
.AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template1"); |
|||
context.Add(template1); |
|||
|
|||
var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout) |
|||
.AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template2"); |
|||
context.Add(template2); |
|||
|
|||
var template3 = new EmailTemplateDefinition("template3", layout: null, singleTemplateFile: true, localizationResource: typeof(AbpEmailingTestResource)) |
|||
.AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl"); |
|||
context.Add(template3); |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +0,0 @@ |
|||
Please confirm your email address by clicking the link below. |
|||
We may need to send you critical information about our service and it is important that we have an accurate email address. |
|||
|
|||
<a href="#">Confirm email address</a> |
|||
@ -1,4 +0,0 @@ |
|||
Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın. |
|||
Size hizmetimizle ilgili kritik bilgileri göndermemiz gerekebilir ve doğru bir e-posta adresimizin olması önemlidir. |
|||
|
|||
<a href="#">E-posta adresini onayla</a> |
|||
@ -1,4 +0,0 @@ |
|||
Please confirm your email address by clicking the link below. |
|||
We may need to send you critical information about our service and it is important that we have an accurate email address. |
|||
|
|||
<a href="#">Confirm email address</a> |
|||
@ -1 +0,0 @@ |
|||
{{#L:hello}} Abp |
|||
@ -1,63 +0,0 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Localization; |
|||
using Shouldly; |
|||
using Volo.Abp.Localization.TestResources.Source; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Testing; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Localization |
|||
{ |
|||
public class TemplateLocalizer_Tests : AbpIntegratedTest<TemplateLocalizer_Tests.TestModule> |
|||
{ |
|||
private readonly ITemplateLocalizer _templateLocalizer; |
|||
private readonly IStringLocalizer<LocalizationTestResource> _testResource; |
|||
|
|||
public TemplateLocalizer_Tests() |
|||
{ |
|||
_testResource = GetRequiredService<IStringLocalizer<LocalizationTestResource>>(); |
|||
_templateLocalizer = GetRequiredService<ITemplateLocalizer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Localize() |
|||
{ |
|||
using (CultureHelper.Use("en")) |
|||
{ |
|||
_templateLocalizer.Localize(_testResource, "<p>{{#L:CarPlural}} <b>{{#L:Universe}}</b></p>") |
|||
.ShouldBe("<p>Cars <b>Universe</b></p>"); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Work_Even_If_No_Text_To_Localize() |
|||
{ |
|||
using (CultureHelper.Use("en")) |
|||
{ |
|||
_templateLocalizer.Localize(_testResource, "<p>test</p>") |
|||
.ShouldBe("<p>test</p>"); |
|||
} |
|||
} |
|||
|
|||
[DependsOn(typeof(AbpTestBaseModule))] |
|||
[DependsOn(typeof(AbpLocalizationModule))] |
|||
public class TestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpLocalization_Tests.TestModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<LocalizationTestResource>("en") |
|||
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Source"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\TextTemplating\Localization\**\*.json" /> |
|||
<None Remove="Volo\Abp\TextTemplating\Localization\**\*.json" /> |
|||
<EmbeddedResource Include="Volo\Abp\TextTemplating\SampleTemplates\**\*.tpl" /> |
|||
<None Remove="Volo\Abp\TextTemplating\SampleTemplates\**\*.tpl" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Localization\Volo.Abp.Localization.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,24 @@ |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase |
|||
{ |
|||
private readonly AbpTextTemplatingOptions _options; |
|||
|
|||
public AbpTextTemplatingOptions_Tests() |
|||
{ |
|||
_options = GetRequiredService<IOptions<AbpTextTemplatingOptions>>().Value; |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Auto_Add_TemplateDefinitionProviders_To_Options() |
|||
{ |
|||
_options |
|||
.DefinitionProviders |
|||
.ShouldContain(typeof(TestTemplateDefinitionProvider)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest<AbpTextTemplatingTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.TextTemplating.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTextTemplatingModule), |
|||
typeof(AbpTestBaseModule), |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpLocalizationModule) |
|||
)] |
|||
public class AbpTextTemplatingTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpTextTemplatingTestModule>("Volo.Abp.TextTemplating"); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<TestLocalizationSource>("en") |
|||
.AddVirtualJson("/Localization"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.TextTemplating.Localization |
|||
{ |
|||
public class TestLocalizationSource |
|||
{ |
|||
} |
|||
} |
|||
@ -1,6 +1,6 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"hello": "hello" |
|||
} |
|||
"HelloText": "Hello" |
|||
} |
|||
} |
|||
@ -1,6 +1,6 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"hello": "Merhaba" |
|||
} |
|||
"HelloText": "Merhaba" |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
{{L "HelloText"}}. Please click to the following link to get an email to reset your password! |
|||
@ -0,0 +1 @@ |
|||
*BEGIN*{{content}}*END* |
|||
@ -0,0 +1 @@ |
|||
Welcome {{model.name}} to the abp.io! |
|||
@ -0,0 +1 @@ |
|||
Merhaba {{model.name}}, abp.io'ya hoşgeldiniz! |
|||
@ -0,0 +1,41 @@ |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateDefinitionTests : AbpTextTemplatingTestBase |
|||
{ |
|||
private readonly ITemplateDefinitionManager _templateDefinitionManager; |
|||
|
|||
public TemplateDefinitionTests() |
|||
{ |
|||
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Retrieve_Template_Definition_By_Name() |
|||
{ |
|||
var welcomeEmailTemplate = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); |
|||
welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); |
|||
welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); |
|||
|
|||
var forgotPasswordEmailTemplate = _templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); |
|||
forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); |
|||
forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Get_Null_If_Template_Not_Found() |
|||
{ |
|||
var definition = _templateDefinitionManager.GetOrNull("undefined-template"); |
|||
definition.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Retrieve_All_Template_Definitions() |
|||
{ |
|||
var definitions = _templateDefinitionManager.GetAll(); |
|||
definitions.Count.ShouldBeGreaterThan(1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TemplateRenderer_Tests : AbpTextTemplatingTestBase |
|||
{ |
|||
private readonly ITemplateRenderer _templateRenderer; |
|||
|
|||
public TemplateRenderer_Tests() |
|||
{ |
|||
_templateRenderer = GetRequiredService<ITemplateRenderer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new |
|||
{ |
|||
name = "John" |
|||
}, |
|||
cultureName: "en" |
|||
)).ShouldBe("Welcome John to the abp.io!"); |
|||
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new |
|||
{ |
|||
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 |
|||
{ |
|||
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 |
|||
{ |
|||
Name = "John" //Intentionally written as PascalCase since Scriban supports it
|
|||
}, |
|||
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_Localized_Template_Content_With_Dictionary_Model() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new Dictionary<string, object>() { { "name", "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, |
|||
cultureName: "en" |
|||
)).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); |
|||
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.ForgotPasswordEmail, |
|||
cultureName: "tr" |
|||
)).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); |
|||
} |
|||
|
|||
private class WelcomeEmailModel |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public WelcomeEmailModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public WelcomeEmailModel(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using Volo.Abp.TextTemplating.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public class TestTemplateDefinitionProvider : TemplateDefinitionProvider |
|||
{ |
|||
public override void Define(ITemplateDefinitionContext context) |
|||
{ |
|||
context.Add( |
|||
new TemplateDefinition( |
|||
TestTemplates.WelcomeEmail, |
|||
defaultCultureName: "en" |
|||
).WithVirtualFilePath("/SampleTemplates/WelcomeEmail") |
|||
); |
|||
|
|||
context.Add( |
|||
new TemplateDefinition( |
|||
TestTemplates.ForgotPasswordEmail, |
|||
localizationResource: typeof(TestLocalizationSource), |
|||
layout: TestTemplates.TestTemplateLayout1 |
|||
).WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl") |
|||
); |
|||
|
|||
context.Add( |
|||
new TemplateDefinition( |
|||
TestTemplates.TestTemplateLayout1, |
|||
isLayout: true |
|||
).WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl") |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
public static class TestTemplates |
|||
{ |
|||
public const string WelcomeEmail = "WelcomeEmail"; |
|||
public const string ForgotPasswordEmail = "ForgotPasswordEmail"; |
|||
public const string TestTemplateLayout1 = "TestTemplateLayout1"; |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
//TODO: Make tests running again!
|
|||
//public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase
|
|||
//{
|
|||
// [Fact]
|
|||
// public async Task Should_Get_Localized_Content_By_Culture()
|
|||
// {
|
|||
// var contributor = new VirtualFileTemplateContentContributor(
|
|||
// "/SampleTemplates/WelcomeEmail"
|
|||
// );
|
|||
|
|||
// contributor.Initialize(
|
|||
// new TemplateContentContributorInitializationContext(
|
|||
// new TemplateDefinition("Test"),
|
|||
// ServiceProvider
|
|||
// )
|
|||
// );
|
|||
|
|||
// (await contributor
|
|||
// .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!");
|
|||
|
|||
// (await contributor
|
|||
// .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!");
|
|||
// }
|
|||
|
|||
// [Fact]
|
|||
// public async Task Should_Get_Non_Localized_Template_Content()
|
|||
// {
|
|||
// var contributor = new VirtualFileTemplateContentContributor(
|
|||
// "/SampleTemplates/ForgotPasswordEmail.tpl"
|
|||
// );
|
|||
|
|||
// contributor.Initialize(
|
|||
// new TemplateContentContributorInitializationContext(
|
|||
// new TemplateDefinition("Test"),
|
|||
// ServiceProvider
|
|||
// )
|
|||
// );
|
|||
|
|||
// (await contributor
|
|||
// .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!");
|
|||
// }
|
|||
//}
|
|||
} |
|||
Loading…
Reference in new issue