EmailTemplate with VirtualFile.

pull/1141/head
maliming 6 years ago
parent 9b4b41573c
commit 5823f8f4cc

@ -14,8 +14,8 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\*.html" />
<None Remove="Volo\Abp\Emailing\Templates\*.html" />
<EmbeddedResource Include="Volo\Abp\Emailing\Templates\DefaultEmailTemplates\*\*.tpl" />
<None Remove="Volo\Abp\Emailing\Templates\DefaultEmailTemplates\*\*.tpl" />
</ItemGroup>
<ItemGroup>

@ -55,5 +55,21 @@ namespace Volo.Abp.Emailing
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
using (var scope = context.ServiceProvider.CreateScope())
{
var emailTemplateDefinitionManager =
scope.ServiceProvider.GetRequiredService<IEmailTemplateDefinitionManager>();
foreach (var templateDefinition in emailTemplateDefinitionManager.GetAll())
{
foreach (var contributor in templateDefinition.Contributors)
{
contributor.Initialize(new EmailTemplateInitializationContext(templateDefinition, scope.ServiceProvider));
}
}
}
}
}
}

@ -1,4 +1,4 @@
using Volo.Abp.Emailing.Templates.Virtual;
using Volo.Abp.Emailing.Templates.VirtualFiles;
namespace Volo.Abp.Emailing.Templates
{
@ -7,10 +7,10 @@ namespace Volo.Abp.Emailing.Templates
public override void Define(IEmailTemplateDefinitionContext context)
{
context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, isLayout: true, layout: null)
.SetVirtualFilePath("/Volo/Abp/Emailing/Templates/DefaultLayout.html"));
.AddTemplateVirtualFile("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout"));
context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage)
.SetVirtualFilePath("/Volo/Abp/Emailing/Templates/SimpleMessageTemplate.html"));
.AddTemplateVirtualFile("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message"));
}
}
}

@ -0,0 +1,22 @@
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;
}
}
}

@ -16,29 +16,18 @@ namespace Volo.Abp.Emailing.Templates
public Type LocalizationResource { get; set; }
public Dictionary<string, object> Properties { 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>
public object this[string name]
{
get => Properties.GetOrDefault(name);
set => Properties[name] = value;
}
public EmailTemplateContributorList Contributors { get; }
public string DefaultCultureName { get; set; }
public EmailTemplateDefinition([NotNull]string name, Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder)
public EmailTemplateDefinition([NotNull]string name, Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
Properties = new Dictionary<string, object>();
LocalizationResource = localizationResource;
Contributors = new EmailTemplateContributorList();
IsLayout = isLayout;
Layout = layout;
DefaultCultureName = defaultCultureName;
}
}
}

@ -0,0 +1,18 @@
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,24 +1,15 @@
using System.Collections.Generic;
using Volo.Abp.Collections;
using Volo.Abp.Emailing.Templates.Virtual;
using Volo.Abp.Collections;
namespace Volo.Abp.Emailing.Templates
{
public class EmailTemplateOptions
{
public List<IEmailTemplateProviderContributor> Providers { get; }
public string DefaultLayout { get; set; }
public ITypeList<IEmailTemplateDefinitionProvider> DefinitionProviders { get; }
public EmailTemplateOptions()
{
Providers = new List<IEmailTemplateProviderContributor>
{
new VirtualFileEmailTemplateProviderContributor()
};
DefaultLayout = StandardEmailTemplates.DefaultLayout;
DefinitionProviders = new TypeList<IEmailTemplateDefinitionProvider>();

@ -1,6 +1,6 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -10,79 +10,91 @@ namespace Volo.Abp.Emailing.Templates
{
public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency
{
protected IServiceProvider ServiceProvider { get; }
protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager;
protected ITemplateLocalizer TemplateLocalizer { get; }
protected EmailTemplateOptions Options { get; }
protected IStringLocalizerFactory StringLocalizerFactory;
public EmailTemplateProvider(
IOptions<EmailTemplateOptions> options,
IServiceProvider serviceProvider,
ITemplateLocalizer templateLocalizer)
public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager,
ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory,
IOptions<EmailTemplateOptions> options)
{
ServiceProvider = serviceProvider;
EmailTemplateDefinitionManager = emailTemplateDefinitionManager;
TemplateLocalizer = templateLocalizer;
StringLocalizerFactory = stringLocalizerFactory;
Options = options.Value;
}
public async Task<EmailTemplate> GetAsync(string name)
public async Task<EmailTemplate> GetAsync(string name, string cultureName)
{
using (var scope = ServiceProvider.CreateScope())
{
return await GetInternalAsync(scope.ServiceProvider, name);
}
return await GetInternalAsync(name, cultureName);
}
protected virtual async Task<EmailTemplate> GetInternalAsync(IServiceProvider serviceProvider, string name)
protected virtual async Task<EmailTemplate> GetInternalAsync(string name, string cultureName)
{
var context = new EmailTemplateProviderContributorContext(name, serviceProvider);
var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name);
if (emailTemplateDefinition == null)
{
// TODO: Localized message
throw new AbpException("email template name not definition");
}
foreach (var provider in Options.Providers)
var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName);
if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null)
{
await provider.ProvideAsync(context);
emailTemplateString =
emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName);
if (emailTemplateString != null)
{
cultureName = emailTemplateDefinition.DefaultCultureName;
}
}
if (context.Template == null)
if (emailTemplateString != null)
{
throw new AbpException($"Could not found the template: {name}");
var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition);
await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName);
await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName);
return emailTemplate;
}
await SetLayoutAsync(serviceProvider, context);
await LocalizeAsync(serviceProvider, context);
return context.Template;
// TODO: Localized message
throw new AbpException($"{cultureName} template not exist!");
}
protected virtual async Task SetLayoutAsync(IServiceProvider serviceProvider, EmailTemplateProviderContributorContext context)
protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition,
EmailTemplate emailTemplate, string cultureName)
{
var layout = context.Template.Definition.Layout;
if (layout.IsNullOrEmpty())
var layout = emailTemplateDefinition.Layout;
if (layout.IsNullOrWhiteSpace())
{
return;
}
if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder)
{
layout = Options.DefaultLayout;
}
var layoutTemplate = await GetInternalAsync(serviceProvider, layout);
context.Template.SetLayout(layoutTemplate);
var layoutTemplate = await GetInternalAsync(layout, cultureName);
emailTemplate.SetLayout(layoutTemplate);
}
protected virtual Task LocalizeAsync(IServiceProvider serviceProvider, EmailTemplateProviderContributorContext context)
protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition,
EmailTemplate emailTemplate, string cultureName)
{
if (context.Template.Definition.LocalizationResource == null)
if (emailTemplateDefinition.LocalizationResource == null)
{
return Task.CompletedTask;
}
var localizer = serviceProvider
.GetRequiredService<IStringLocalizerFactory>()
.Create(context.Template.Definition.LocalizationResource);
var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource);
context.Template.SetContent(
TemplateLocalizer.Localize(localizer, context.Template.Content)
emailTemplate.SetContent(
TemplateLocalizer.Localize(localizer.WithCulture(new CultureInfo(cultureName)), emailTemplate.Content)
);
return Task.CompletedTask;

@ -1,20 +0,0 @@
using System;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Emailing.Templates
{
public class EmailTemplateProviderContributorContext : IServiceProviderAccessor
{
public string Name { get; }
public IServiceProvider ServiceProvider { get; }
public EmailTemplate Template { get; set; }
public EmailTemplateProviderContributorContext(string name, IServiceProvider serviceProvider)
{
Name = name;
ServiceProvider = serviceProvider;
}
}
}

@ -0,0 +1,9 @@
namespace Volo.Abp.Emailing.Templates
{
public interface IEmailTemplateContributor
{
void Initialize(EmailTemplateInitializationContext context);
string GetOrNull(string cultureName);
}
}

@ -4,6 +4,6 @@ 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 IEmailTemplateProviderContributor
{
Task ProvideAsync(EmailTemplateProviderContributorContext contributorContext);
}
}

@ -1,16 +0,0 @@
namespace Volo.Abp.Emailing.Templates.Virtual
{
public static class EmailTemplateDefinitionExtensions
{
public static EmailTemplateDefinition SetVirtualFilePath(this EmailTemplateDefinition emailTemplateDefinition, string path)
{
emailTemplateDefinition[VirtualFileEmailTemplateProviderContributor.VirtualFilePathKey] = path;
return emailTemplateDefinition;
}
public static string GetVirtualFilePathOrNull(this EmailTemplateDefinition emailTemplateDefinition)
{
return emailTemplateDefinition[VirtualFileEmailTemplateProviderContributor.VirtualFilePathKey] as string;
}
}
}

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.Emailing.Templates.Virtual
{
public class VirtualFileEmailTemplateOptions
{
public IDictionary<string, string> Templates { get; }
public VirtualFileEmailTemplateOptions()
{
Templates = new Dictionary<string, string>();
}
}
}

@ -1,59 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Emailing.Templates.Virtual
{
public class VirtualFileEmailTemplateProviderContributor : IEmailTemplateProviderContributor
{
public const string VirtualFilePathKey = "VirtualFilePath";
public Task ProvideAsync(EmailTemplateProviderContributorContext contributorContext)
{
var templateDefinition = FindTemplateDefinition(contributorContext);
if (templateDefinition == null)
{
return Task.CompletedTask;
}
var fileInfo = FindVirtualFileInfo(contributorContext, templateDefinition);
if (fileInfo == null)
{
return Task.CompletedTask;
}
contributorContext.Template = new EmailTemplate(fileInfo.ReadAsString(), templateDefinition);
return Task.CompletedTask;
}
protected virtual EmailTemplateDefinition FindTemplateDefinition(EmailTemplateProviderContributorContext contributorContext)
{
return contributorContext
.ServiceProvider
.GetRequiredService<IEmailTemplateDefinitionManager>()
.GetOrNull(contributorContext.Name);
}
protected virtual IFileInfo FindVirtualFileInfo(EmailTemplateProviderContributorContext contributorContext, EmailTemplateDefinition templateDefinition)
{
var virtualFilePath = templateDefinition?.GetVirtualFilePathOrNull();
if (virtualFilePath == null)
{
return null;
}
var virtualFileProvider = contributorContext.ServiceProvider.GetRequiredService<IVirtualFileProvider>();
var fileInfo = virtualFileProvider.GetFileInfo(virtualFilePath);
if (fileInfo?.Exists != true)
{
return null;
}
return fileInfo;
}
}
}

@ -0,0 +1,12 @@
namespace Volo.Abp.Emailing.Templates.VirtualFiles
{
public static class EmailTemplateDefinitionExtensions
{
public static EmailTemplateDefinition AddTemplateVirtualFile(
this EmailTemplateDefinition emailTemplateDefinition, string path)
{
emailTemplateDefinition.Contributors.Add(new VirtualFileEmailTemplateContributor(path));
return emailTemplateDefinition;
}
}
}

@ -0,0 +1,68 @@
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 VirtualFileEmailTemplateContributor : IEmailTemplateContributor
{
private readonly string _virtualPath;
private IVirtualFileProvider _virtualFileProvider;
private Dictionary<string, string> _templateDictionary;
private readonly object _syncObj = new object();
public VirtualFileEmailTemplateContributor(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,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
@ -11,8 +11,8 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Abp\Emailing\TestTemplates\*.*" />
<None Remove="Volo\Abp\Emailing\TestTemplates\*.*" />
<EmbeddedResource Include="Volo\Abp\Emailing\TestTemplates\*\*.tpl" />
<None Remove="Volo\Abp\Emailing\TestTemplates\*\*.tpl" />
</ItemGroup>
<ItemGroup>

@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Emailing.Templates;
using Xunit;
@ -22,8 +22,23 @@ namespace Volo.Abp.Emailing
[Fact]
public async Task Should_Get_Registered_Template()
{
var template = await _emailTemplateProvider.GetAsync("template1");
template.Content.ShouldContain("This is a test 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>\r\n " + "Please confirm your email address by clicking the link below.");
}
}
}

@ -1,5 +1,5 @@
using Volo.Abp.Emailing.Templates;
using Volo.Abp.Emailing.Templates.Virtual;
using Volo.Abp.Emailing.Templates.VirtualFiles;
namespace Volo.Abp.Emailing
{
@ -7,8 +7,13 @@ namespace Volo.Abp.Emailing
{
public override void Define(IEmailTemplateDefinitionContext context)
{
context.Add(new EmailTemplateDefinition("template1")
.SetVirtualFilePath("/Volo/Abp/Emailing/TestTemplates/template1.html"));
var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null)
.AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template1");
context.Add(template1);
var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout)
.AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template2");
context.Add(template2);
}
}
}

@ -0,0 +1,4 @@
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>

@ -0,0 +1,4 @@
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>

@ -0,0 +1,4 @@
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,9 +0,0 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
This is a test template!
</body>
</html>
Loading…
Cancel
Save