mirror of https://github.com/abpframework/abp.git
83 changed files with 1401 additions and 119 deletions
@ -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,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpVirtualFileSystemModule), |
|||
typeof(AbpLocalizationAbstractionsModule) |
|||
)] |
|||
public class AbpTextTemplatingAbstractionsModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
AutoAddProvidersAndContributors(context.Services); |
|||
} |
|||
|
|||
private static void AutoAddProvidersAndContributors(IServiceCollection services) |
|||
{ |
|||
var definitionProviders = new List<Type>(); |
|||
var contentContributors = new List<Type>(); |
|||
|
|||
services.OnRegistred(context => |
|||
{ |
|||
if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
definitionProviders.Add(context.ImplementationType); |
|||
} |
|||
|
|||
if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
contentContributors.Add(context.ImplementationType); |
|||
} |
|||
}); |
|||
|
|||
services.Configure<AbpTextTemplatingOptions>(options => |
|||
{ |
|||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
|||
options.ContentContributors.AddIfNotContains(contentContributors); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,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,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="$(MicrosoftPackageVersion)" /> |
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTextTemplatingAbstractionsModule) |
|||
)] |
|||
public class AbpTextTemplatingRazorModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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 CSharpCompiler : ISingletonDependency |
|||
{ |
|||
protected CSharpCompilerOptions Options { get; } |
|||
|
|||
public CSharpCompiler(IOptions<CSharpCompilerOptions> options) |
|||
{ |
|||
Options = options.Value; |
|||
} |
|||
|
|||
private static IEnumerable<PortableExecutableReference> DefaultReferences => new List<Assembly>() |
|||
{ |
|||
Assembly.Load("netstandard"), |
|||
Assembly.Load("System.Private.CoreLib"), |
|||
Assembly.Load("System.Runtime"), |
|||
Assembly.Load("System.Collections"), |
|||
Assembly.Load("System.ComponentModel"), |
|||
Assembly.Load("System.Linq"), |
|||
Assembly.Load("System.Linq.Expressions"), |
|||
Assembly.Load("Microsoft.Extensions.DependencyInjection"), |
|||
Assembly.Load("Microsoft.Extensions.DependencyInjection.Abstractions"), |
|||
Assembly.Load("Microsoft.Extensions.Localization"), |
|||
Assembly.Load("Microsoft.Extensions.Localization.Abstractions"), |
|||
|
|||
typeof(CSharpCompiler).Assembly |
|||
} |
|||
.Select(x => MetadataReference.CreateFromFile(x.Location)) |
|||
.ToImmutableList(); |
|||
|
|||
public virtual Stream CreateAssembly(string code, string assemblyName, List<MetadataReference> references = null, CSharpCompilationOptions options = null) |
|||
{ |
|||
var defaultReferences = DefaultReferences.Concat(Options.References); |
|||
try |
|||
{ |
|||
var compilation = CSharpCompilation.Create( |
|||
assemblyName, |
|||
syntaxTrees: new[] { CreateSyntaxTree(code) }, |
|||
references: references != null ? defaultReferences.Concat(references) : defaultReferences, |
|||
options ?? GetCompilationOptions()); |
|||
|
|||
using (var memoryStream = new MemoryStream()) |
|||
{ |
|||
var result = compilation.Emit(memoryStream); |
|||
|
|||
if (!result.Success) |
|||
{ |
|||
var error = new StringBuilder(); |
|||
error.AppendLine("Build failed"); |
|||
foreach (var diagnostic in result.Diagnostics) |
|||
{ |
|||
error.AppendLine(diagnostic.ToString()); |
|||
} |
|||
|
|||
throw new Exception(error.ToString()); |
|||
} |
|||
|
|||
memoryStream.Seek(0, SeekOrigin.Begin); |
|||
var assemblyStream = new MemoryStream(); |
|||
memoryStream.CopyTo(assemblyStream); |
|||
|
|||
return assemblyStream; |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
var error = new StringBuilder(); |
|||
error.AppendLine("CreateAssembly failed"); |
|||
error.AppendLine(e.Message); |
|||
throw new Exception(error.ToString()); |
|||
} |
|||
} |
|||
|
|||
protected virtual SyntaxTree CreateSyntaxTree(string code) |
|||
{ |
|||
return CSharpSyntaxTree.ParseText(code); |
|||
} |
|||
|
|||
protected virtual CSharpCompilationOptions GetCompilationOptions() |
|||
{ |
|||
var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); |
|||
|
|||
// Disable 1702 until roslyn turns this off by default
|
|||
csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions( |
|||
new Dictionary<string, ReportDiagnostic> |
|||
{ |
|||
{"CS1701", ReportDiagnostic.Suppress}, // Binding redirects
|
|||
{"CS1702", ReportDiagnostic.Suppress}, |
|||
{"CS1705", ReportDiagnostic.Suppress} |
|||
}); |
|||
|
|||
return csharpCompilationOptions.WithOptimizationLevel(OptimizationLevel.Release); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.CodeAnalysis; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class CSharpCompilerOptions |
|||
{ |
|||
public List<PortableExecutableReference> References { get; } |
|||
|
|||
public CSharpCompilerOptions() |
|||
{ |
|||
References = new List<PortableExecutableReference>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Razor.Language; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class DefaultCompiledViewProvider : ICompiledViewProvider, ITransientDependency |
|||
{ |
|||
private static readonly ConcurrentDictionary<string, Assembly> CachedAssembles = new ConcurrentDictionary<string, Assembly>(); |
|||
|
|||
private readonly CSharpCompiler _cSharpCompiler; |
|||
private readonly IRazorProjectEngineFactory _razorProjectEngineFactory; |
|||
private readonly ITemplateContentProvider _templateContentProvider; |
|||
|
|||
public DefaultCompiledViewProvider( |
|||
IRazorProjectEngineFactory razorProjectEngineFactory, |
|||
CSharpCompiler cSharpCompiler, |
|||
ITemplateContentProvider templateContentProvider) |
|||
{ |
|||
_razorProjectEngineFactory = razorProjectEngineFactory; |
|||
_cSharpCompiler = cSharpCompiler; |
|||
_templateContentProvider = templateContentProvider; |
|||
} |
|||
|
|||
public virtual async Task<Assembly> GetAssemblyAsync(TemplateDefinition templateDefinition) |
|||
{ |
|||
async Task<Assembly> CreateAssembly(string content) |
|||
{ |
|||
using (var assemblyStream = await GetAssemblyStreamAsync(templateDefinition, content)) |
|||
{ |
|||
return Assembly.Load(await assemblyStream.GetAllBytesAsync()); |
|||
} |
|||
} |
|||
|
|||
var templateContent = await _templateContentProvider.GetContentOrNullAsync(templateDefinition); |
|||
return CachedAssembles.GetOrAdd((templateDefinition.Name + templateContent).ToMd5(), await CreateAssembly(templateContent)); |
|||
} |
|||
|
|||
protected virtual async Task<Stream> GetAssemblyStreamAsync(TemplateDefinition templateDefinition, string templateContent) |
|||
{ |
|||
var razorProjectEngine = await _razorProjectEngineFactory.CreateAsync(builder => |
|||
{ |
|||
builder.SetNamespace(AbpRazorTemplateConsts.DefaultNameSpace); |
|||
builder.ConfigureClass((document, node) => |
|||
{ |
|||
node.ClassName = AbpRazorTemplateConsts.DefaultClassName; |
|||
}); |
|||
}); |
|||
|
|||
var codeDocument = razorProjectEngine.Process( |
|||
RazorSourceDocument.Create(templateContent, templateDefinition.Name), null, |
|||
new List<RazorSourceDocument>(), new List<TagHelperDescriptor>()); |
|||
|
|||
var cSharpDocument = codeDocument.GetCSharpDocument(); |
|||
|
|||
return _cSharpCompiler.CreateAssembly(cSharpDocument.GeneratedCode, templateDefinition.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -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 DefaultRazorProjectEngineFactory : IRazorProjectEngineFactory, ITransientDependency |
|||
{ |
|||
public virtual async Task<RazorProjectEngine> CreateAsync(Action<RazorProjectEngineBuilder> configure = null) |
|||
{ |
|||
return RazorProjectEngine.Create(await CreateRazorConfigurationAsync(), EmptyProjectFileSystem.Empty, configure); |
|||
} |
|||
|
|||
protected virtual Task<RazorConfiguration> CreateRazorConfigurationAsync() |
|||
{ |
|||
return Task.FromResult(RazorConfiguration.Default); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.AspNetCore.Razor.Language; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
internal class EmptyProjectFileSystem : RazorProjectFileSystem |
|||
{ |
|||
internal static readonly RazorProjectFileSystem Empty = new EmptyProjectFileSystem(); |
|||
|
|||
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath) |
|||
{ |
|||
NormalizeAndEnsureValidPath(basePath); |
|||
return Enumerable.Empty<RazorProjectItem>(); |
|||
} |
|||
|
|||
[Obsolete("Use GetItem(string path, string fileKind) instead.")] |
|||
public override RazorProjectItem GetItem(string path) |
|||
{ |
|||
return GetItem(path, fileKind: null); |
|||
} |
|||
|
|||
public override RazorProjectItem GetItem(string path, string fileKind) |
|||
{ |
|||
NormalizeAndEnsureValidPath(path); |
|||
return new NotFoundProjectItem(string.Empty, path, fileKind); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public interface ICompiledViewProvider |
|||
{ |
|||
Task<Assembly> GetAssemblyAsync(TemplateDefinition templateDefinition); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Razor.Language; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public interface IRazorProjectEngineFactory |
|||
{ |
|||
Task<RazorProjectEngine> CreateAsync(Action<RazorProjectEngineBuilder> configure = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text.Encodings.Web; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public interface IRazorTemplatePage<TModel> : IRazorTemplatePage |
|||
{ |
|||
TModel Model { get; set; } |
|||
} |
|||
|
|||
public interface IRazorTemplatePage |
|||
{ |
|||
IServiceProvider ServiceProvider { get; set; } |
|||
|
|||
IStringLocalizer Localizer { get; set; } |
|||
|
|||
HtmlEncoder HtmlEncoder { get; set; } |
|||
|
|||
JavaScriptEncoder JavaScriptEncoder { get; set; } |
|||
|
|||
UrlEncoder UrlEncoder { get; set; } |
|||
|
|||
Dictionary<string, object> GlobalContext { get; set; } |
|||
|
|||
string Body { get; set; } |
|||
|
|||
Task ExecuteAsync(); |
|||
|
|||
Task<string> GetOutputAsync(); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,177 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Localization; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public abstract class RazorTemplatePageBase<TModel> : RazorTemplatePageBase, IRazorTemplatePage<TModel> |
|||
{ |
|||
public TModel Model { get; set; } |
|||
} |
|||
|
|||
public abstract class RazorTemplatePageBase : IRazorTemplatePage |
|||
{ |
|||
public Dictionary<string, object> GlobalContext { get; set; } |
|||
|
|||
public IServiceProvider ServiceProvider { get; set; } |
|||
|
|||
public IStringLocalizer Localizer { get; set; } |
|||
|
|||
public HtmlEncoder HtmlEncoder { get; set; } |
|||
|
|||
public JavaScriptEncoder JavaScriptEncoder { get; set; } |
|||
|
|||
public UrlEncoder UrlEncoder { get; set; } |
|||
|
|||
public string Body { get; set; } |
|||
|
|||
private readonly StringBuilder _stringBuilder = new StringBuilder(); |
|||
|
|||
private AttributeInfo _attributeInfo; |
|||
|
|||
public virtual void WriteLiteral(string literal = null) |
|||
{ |
|||
_stringBuilder.Append(literal); |
|||
} |
|||
|
|||
public virtual void Write(object obj = null) |
|||
{ |
|||
_stringBuilder.Append(obj); |
|||
} |
|||
|
|||
public virtual void BeginWriteAttribute(string name, string prefix, int prefixOffset, string suffix, int suffixOffset, int attributeValuesCount) |
|||
{ |
|||
_attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount); |
|||
WriteLiteral(prefix); |
|||
} |
|||
|
|||
public virtual void WriteAttributeValue(string prefix, int prefixOffset, object value, int valueOffset, int valueLength, bool isLiteral) |
|||
{ |
|||
if (_attributeInfo.AttributeValuesCount == 1) |
|||
{ |
|||
if (IsBoolFalseOrNullValue(prefix, value)) |
|||
{ |
|||
// Value is either null or the bool 'false' with no prefix; don't render the attribute.
|
|||
_attributeInfo.Suppressed = true; |
|||
return; |
|||
} |
|||
|
|||
// We are not omitting the attribute. Write the prefix.
|
|||
WriteLiteral(_attributeInfo.Prefix); |
|||
|
|||
if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) |
|||
{ |
|||
// The value is just the bool 'true', write the attribute name instead of the string 'True'.
|
|||
value = _attributeInfo.Name; |
|||
} |
|||
} |
|||
|
|||
// This block handles two cases.
|
|||
// 1. Single value with prefix.
|
|||
// 2. Multiple values with or without prefix.
|
|||
if (value != null) |
|||
{ |
|||
if (!string.IsNullOrEmpty(prefix)) |
|||
{ |
|||
WriteLiteral(prefix); |
|||
} |
|||
|
|||
WriteUnprefixedAttributeValue(value, isLiteral); |
|||
} |
|||
|
|||
_stringBuilder.Append(prefix); |
|||
_stringBuilder.Append(value); |
|||
} |
|||
|
|||
public virtual void EndWriteAttribute() |
|||
{ |
|||
if (!_attributeInfo.Suppressed) |
|||
{ |
|||
WriteLiteral(_attributeInfo.Suffix); |
|||
} |
|||
} |
|||
|
|||
public virtual Task ExecuteAsync() |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public virtual Task<string> GetOutputAsync() |
|||
{ |
|||
return Task.FromResult(_stringBuilder.ToString()); |
|||
} |
|||
|
|||
private void WriteUnprefixedAttributeValue(object value, bool isLiteral) |
|||
{ |
|||
var stringValue = value as string; |
|||
|
|||
// The extra branching here is to ensure that we call the Write*To(string) overload where possible.
|
|||
if (isLiteral && stringValue != null) |
|||
{ |
|||
WriteLiteral(stringValue); |
|||
} |
|||
else if (isLiteral) |
|||
{ |
|||
//WriteLiteral(value);
|
|||
_stringBuilder.Append(value); |
|||
} |
|||
else if (stringValue != null) |
|||
{ |
|||
Write(stringValue); |
|||
} |
|||
else |
|||
{ |
|||
Write(value); |
|||
} |
|||
} |
|||
|
|||
private bool IsBoolFalseOrNullValue(string prefix, object value) |
|||
{ |
|||
return string.IsNullOrEmpty(prefix) && (value == null || (value is bool b && !b)); |
|||
} |
|||
|
|||
private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value) |
|||
{ |
|||
// If the value is just the bool 'true', use the attribute name as the value.
|
|||
return string.IsNullOrEmpty(prefix) && (value is bool b && b); |
|||
} |
|||
|
|||
private struct AttributeInfo |
|||
{ |
|||
public AttributeInfo( |
|||
string name, |
|||
string prefix, |
|||
int prefixOffset, |
|||
string suffix, |
|||
int suffixOffset, |
|||
int attributeValuesCount) |
|||
{ |
|||
Name = name; |
|||
Prefix = prefix; |
|||
PrefixOffset = prefixOffset; |
|||
Suffix = suffix; |
|||
SuffixOffset = suffixOffset; |
|||
AttributeValuesCount = attributeValuesCount; |
|||
|
|||
Suppressed = false; |
|||
} |
|||
|
|||
public int AttributeValuesCount { get; } |
|||
|
|||
public string Name { get; } |
|||
|
|||
public string Prefix { get; } |
|||
|
|||
public int PrefixOffset { get; } |
|||
|
|||
public string Suffix { get; } |
|||
|
|||
public int SuffixOffset { get; } |
|||
|
|||
public bool Suppressed { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -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 ICompiledViewProvider _compiledViewProvider; |
|||
private readonly ITemplateDefinitionManager _templateDefinitionManager; |
|||
private readonly IStringLocalizerFactory _stringLocalizerFactory; |
|||
|
|||
public RazorTemplateRenderer( |
|||
IServiceScopeFactory serviceScopeFactory, |
|||
ICompiledViewProvider compiledViewProvider, |
|||
ITemplateDefinitionManager templateDefinitionManager, |
|||
IStringLocalizerFactory stringLocalizerFactory) |
|||
{ |
|||
_serviceScopeFactory = serviceScopeFactory; |
|||
_templateDefinitionManager = templateDefinitionManager; |
|||
_stringLocalizerFactory = stringLocalizerFactory; |
|||
_compiledViewProvider = compiledViewProvider; |
|||
} |
|||
|
|||
public virtual async Task<string> RenderAsync( |
|||
[NotNull] string templateName, |
|||
[CanBeNull] object model = null, |
|||
[CanBeNull] string cultureName = null, |
|||
[CanBeNull] Dictionary<string, object> globalContext = null) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); |
|||
|
|||
if (globalContext == null) |
|||
{ |
|||
globalContext = new Dictionary<string, object>(); |
|||
} |
|||
|
|||
if (cultureName == null) |
|||
{ |
|||
return await RenderInternalAsync( |
|||
templateName, |
|||
null, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
else |
|||
{ |
|||
using (CultureHelper.Use(cultureName)) |
|||
{ |
|||
return await RenderInternalAsync( |
|||
templateName, |
|||
null, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderInternalAsync( |
|||
string templateName, |
|||
string body, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var templateDefinition = _templateDefinitionManager.Get(templateName); |
|||
|
|||
var renderedContent = await RenderSingleTemplateAsync( |
|||
templateDefinition, |
|||
body, |
|||
globalContext, |
|||
model |
|||
); |
|||
|
|||
if (templateDefinition.Layout != null) |
|||
{ |
|||
renderedContent = await RenderInternalAsync( |
|||
templateDefinition.Layout, |
|||
renderedContent, |
|||
globalContext |
|||
); |
|||
} |
|||
|
|||
return renderedContent; |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderSingleTemplateAsync( |
|||
TemplateDefinition templateDefinition, |
|||
string body, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
return await RenderTemplateContentWithRazorAsync( |
|||
templateDefinition, |
|||
body, |
|||
globalContext, |
|||
model |
|||
); |
|||
} |
|||
|
|||
protected virtual async Task<string> RenderTemplateContentWithRazorAsync( |
|||
TemplateDefinition templateDefinition, |
|||
string body, |
|||
Dictionary<string, object> globalContext, |
|||
object model = null) |
|||
{ |
|||
var assembly = await _compiledViewProvider.GetAssemblyAsync(templateDefinition); |
|||
var templateType = assembly.GetType(AbpRazorTemplateConsts.TypeName); |
|||
var template = (IRazorTemplatePage) Activator.CreateInstance(templateType); |
|||
|
|||
using (var scope = _serviceScopeFactory.CreateScope()) |
|||
{ |
|||
var modelType = templateType |
|||
.GetInterfaces() |
|||
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRazorTemplatePage<>)) |
|||
.Select(x => x.GenericTypeArguments.FirstOrDefault()) |
|||
.FirstOrDefault(); |
|||
|
|||
if (modelType != null) |
|||
{ |
|||
GetType().GetMethod(nameof(SetModel), BindingFlags.Instance | BindingFlags.NonPublic) |
|||
?.MakeGenericMethod(modelType).Invoke(this, new[] {template, model}); |
|||
} |
|||
|
|||
template.ServiceProvider = scope.ServiceProvider; |
|||
template.Localizer = GetLocalizerOrNull(templateDefinition); |
|||
template.HtmlEncoder = scope.ServiceProvider.GetService<HtmlEncoder>(); |
|||
template.JavaScriptEncoder = scope.ServiceProvider.GetService<JavaScriptEncoder>(); |
|||
template.UrlEncoder = scope.ServiceProvider.GetService<UrlEncoder>(); |
|||
template.Body = body; |
|||
template.GlobalContext = globalContext; |
|||
|
|||
await template.ExecuteAsync(); |
|||
|
|||
return await template.GetOutputAsync(); |
|||
} |
|||
} |
|||
|
|||
private void SetModel<TModel>(IRazorTemplatePage razorTemplatePage, object model = null) |
|||
{ |
|||
if (razorTemplatePage is IRazorTemplatePage<TModel> razorTemplatePageWithModel) |
|||
{ |
|||
razorTemplatePageWithModel.Model = (TModel)model; |
|||
} |
|||
} |
|||
|
|||
private IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition) |
|||
{ |
|||
if (templateDefinition.LocalizationResource != null) |
|||
{ |
|||
return _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); |
|||
} |
|||
|
|||
return _stringLocalizerFactory.CreateDefaultOrNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -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,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.TextTemplating.Abstractions\Volo.Abp.TextTemplating.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Scriban" Version="3.6.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Scriban |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTextTemplatingAbstractionsModule) |
|||
)] |
|||
public class AbpTextTemplatingScribanModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -1,46 +1,9 @@ |
|||
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; |
|||
|
|||
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,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net5.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\TextTemplating\Razor\SampleTemplates\**\*.cshtml"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</EmbeddedResource> |
|||
<None Remove="Volo\Abp\TextTemplating\Razor\SampleTemplates\**\*.cshtml" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Razor\Volo.Abp.TextTemplating.Razor.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.IO; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.TextTemplating.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class RazorLocalizedTemplateContentReaderFactory_Tests : LocalizedTemplateContentReaderFactory_Tests<RazorTextTemplatingTestModule> |
|||
{ |
|||
public RazorLocalizedTemplateContentReaderFactory_Tests() |
|||
{ |
|||
LocalizedTemplateContentReaderFactory = new LocalizedTemplateContentReaderFactory( |
|||
new PhysicalFileVirtualFileProvider( |
|||
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), |
|||
"Volo", "Abp", "TextTemplating", "Razor")))); |
|||
|
|||
WelcomeEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" + |
|||
Environment.NewLine + |
|||
"Welcome @Model.Name to the abp.io!"; |
|||
|
|||
WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" + |
|||
Environment.NewLine + |
|||
"Merhaba @Model.Name, abp.io'ya hoşgeldiniz!"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class RazorTemplateDefinitionTests : TemplateDefinitionTests<RazorTextTemplatingTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class RazorTemplateRenderer_Tests : AbpTextTemplatingTestBase<RazorTextTemplatingTestModule> |
|||
{ |
|||
private readonly ITemplateRenderer _templateRenderer; |
|||
|
|||
public RazorTemplateRenderer_Tests() |
|||
{ |
|||
_templateRenderer = GetRequiredService<ITemplateRenderer>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new WelcomeEmailModel() |
|||
{ |
|||
Name = "John" |
|||
}, |
|||
cultureName: "en" |
|||
)).ShouldBe("Welcome John to the abp.io!"); |
|||
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new WelcomeEmailModel() |
|||
{ |
|||
Name = "John" |
|||
}, |
|||
cultureName: "tr" |
|||
)).ShouldBe("Merhaba John, abp.io'ya hoşgeldiniz!"); |
|||
|
|||
//"en-US" fallbacks to "en" since "en-US" doesn't exists and "en" is the fallback culture
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new WelcomeEmailModel() |
|||
{ |
|||
Name = "John" |
|||
}, |
|||
cultureName: "en-US" |
|||
)).ShouldBe("Welcome John to the abp.io!"); |
|||
|
|||
//"fr" fallbacks to "en" since "fr" doesn't exists and "en" is the default culture
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new WelcomeEmailModel() |
|||
{ |
|||
Name = "John" |
|||
}, |
|||
cultureName: "fr" |
|||
)).ShouldBe("Welcome John to the abp.io!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Rendered_Localized_Template_Content_With_Stronly_Typed_Model() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.WelcomeEmail, |
|||
model: new WelcomeEmailModel("John"), |
|||
cultureName: "en" |
|||
)).ShouldBe("Welcome John to the abp.io!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Rendered_Inline_Localized_Template() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.ForgotPasswordEmail, |
|||
new ForgotPasswordEmailModel("John"), |
|||
cultureName: "en" |
|||
)).ShouldBe("*BEGIN*Hello John, how are you?. Please click to the following link to get an email to reset your password!*END*"); |
|||
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.ForgotPasswordEmail, |
|||
new ForgotPasswordEmailModel("John"), |
|||
cultureName: "tr" |
|||
)).ShouldBe("*BEGIN*Merhaba John, nasılsın?. Please click to the following link to get an email to reset your password!*END*"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Localized_Numbers() |
|||
{ |
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.ShowDecimalNumber, |
|||
new ShowDecimalNumberModel(123.45M), |
|||
cultureName: "en" |
|||
)).ShouldBe("*BEGIN*123.45*END*"); |
|||
|
|||
(await _templateRenderer.RenderAsync( |
|||
TestTemplates.ShowDecimalNumber, |
|||
new ShowDecimalNumberModel(123.45M), |
|||
cultureName: "de" |
|||
)).ShouldBe("*BEGIN*123,45*END*"); |
|||
} |
|||
|
|||
public class WelcomeEmailModel |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public WelcomeEmailModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public WelcomeEmailModel(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
|
|||
public class ForgotPasswordEmailModel |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public ForgotPasswordEmailModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ForgotPasswordEmailModel(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
|
|||
public class ShowDecimalNumberModel |
|||
{ |
|||
public decimal Amount { get; set; } |
|||
|
|||
public ShowDecimalNumberModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ShowDecimalNumberModel(decimal amount) |
|||
{ |
|||
Amount = amount; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using Microsoft.CodeAnalysis; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTextTemplatingRazorModule), |
|||
typeof(AbpTextTemplatingTestModule) |
|||
)] |
|||
public class RazorTextTemplatingTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<RazorTextTemplatingTestModule>("Volo.Abp.TextTemplating.Razor"); |
|||
}); |
|||
|
|||
Configure<CSharpCompilerOptions>(options => |
|||
{ |
|||
options.References.Add(MetadataReference.CreateFromFile(typeof(RazorTextTemplatingTestModule).Assembly.Location)); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using Volo.Abp.TextTemplating.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Razor |
|||
{ |
|||
public class RazorVirtualFileTemplateContributor_Tests : VirtualFileTemplateContributor_Tests<RazorTextTemplatingTestModule> |
|||
{ |
|||
public RazorVirtualFileTemplateContributor_Tests() |
|||
{ |
|||
WelcomeEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" + |
|||
Environment.NewLine + |
|||
"Welcome @Model.Name to the abp.io!"; |
|||
|
|||
WelcomeEmailTurkishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel>" + |
|||
Environment.NewLine + |
|||
"Merhaba @Model.Name, abp.io'ya hoşgeldiniz!"; |
|||
|
|||
ForgotPasswordEmailEnglishContent = "@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ForgotPasswordEmailModel>" + |
|||
Environment.NewLine + |
|||
"@Localizer[\"HelloText\", Model.Name], @Localizer[\"HowAreYou\"]. Please click to the following link to get an email to reset your password!"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ForgotPasswordEmailModel> |
|||
@Localizer["HelloText", Model.Name], @Localizer["HowAreYou"]. Please click to the following link to get an email to reset your password! |
|||
@ -0,0 +1,2 @@ |
|||
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.ShowDecimalNumberModel> |
|||
@Model.Amount |
|||
@ -0,0 +1,2 @@ |
|||
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase |
|||
*BEGIN*@Body*END* |
|||
@ -0,0 +1,2 @@ |
|||
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel> |
|||
Welcome @Model.Name to the abp.io! |
|||
@ -0,0 +1,2 @@ |
|||
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase<Volo.Abp.TextTemplating.Razor.RazorTemplateRenderer_Tests.WelcomeEmailModel> |
|||
Merhaba @Model.Name, abp.io'ya hoşgeldiniz! |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net5.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Volo\Abp\TextTemplating\Scriban\SampleTemplates\**\*.tpl"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</EmbeddedResource> |
|||
<None Remove="Volo\Abp\TextTemplating\Scriban\SampleTemplates\**\*.tpl" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Scriban\Volo.Abp.TextTemplating.Scriban.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,20 @@ |
|||
using System.IO; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.TextTemplating.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Scriban |
|||
{ |
|||
public class ScribanLocalizedTemplateContentReaderFactory_Tests : LocalizedTemplateContentReaderFactory_Tests<ScribanTextTemplatingTestModule> |
|||
{ |
|||
public ScribanLocalizedTemplateContentReaderFactory_Tests() |
|||
{ |
|||
LocalizedTemplateContentReaderFactory = new LocalizedTemplateContentReaderFactory( |
|||
new PhysicalFileVirtualFileProvider( |
|||
new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), |
|||
"Volo", "Abp", "TextTemplating", "Scriban")))); |
|||
|
|||
WelcomeEmailEnglishContent = "Welcome {{model.name}} to the abp.io!"; |
|||
WelcomeEmailTurkishContent = "Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.TextTemplating.Scriban |
|||
{ |
|||
public class ScribanTemplateDefinitionTests : TemplateDefinitionTests<ScribanTextTemplatingTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Scriban |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTextTemplatingScribanModule), |
|||
typeof(AbpTextTemplatingTestModule) |
|||
)] |
|||
public class ScribanTextTemplatingTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<ScribanTextTemplatingTestModule>("Volo.Abp.TextTemplating.Scriban"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.TextTemplating.VirtualFiles; |
|||
|
|||
namespace Volo.Abp.TextTemplating.Scriban |
|||
{ |
|||
public class ScribanVirtualFileTemplateContributor_Tests : VirtualFileTemplateContributor_Tests<ScribanTextTemplatingTestModule> |
|||
{ |
|||
public ScribanVirtualFileTemplateContributor_Tests() |
|||
{ |
|||
WelcomeEmailEnglishContent = "Welcome {{model.name}} to the abp.io!"; |
|||
WelcomeEmailTurkishContent = "Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"; |
|||
ForgotPasswordEmailEnglishContent = "{{L \"HelloText\" model.name}}, {{L \"HowAreYou\" }}. Please click to the following link to get an email to reset your password!"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,45 +1,50 @@ |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Modularity; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.TextTemplating.VirtualFiles |
|||
{ |
|||
public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase |
|||
public abstract class VirtualFileTemplateContributor_Tests<TStartupModule> : AbpTextTemplatingTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
private readonly ITemplateDefinitionManager _templateDefinitionManager; |
|||
private readonly VirtualFileTemplateContentContributor _virtualFileTemplateContentContributor; |
|||
protected readonly ITemplateDefinitionManager TemplateDefinitionManager; |
|||
protected readonly VirtualFileTemplateContentContributor VirtualFileTemplateContentContributor; |
|||
protected string WelcomeEmailEnglishContent; |
|||
protected string WelcomeEmailTurkishContent; |
|||
protected string ForgotPasswordEmailEnglishContent; |
|||
|
|||
public VirtualFileTemplateContributor_Tests() |
|||
protected VirtualFileTemplateContributor_Tests() |
|||
{ |
|||
_templateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>(); |
|||
_virtualFileTemplateContentContributor = GetRequiredService<VirtualFileTemplateContentContributor>(); |
|||
TemplateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>(); |
|||
VirtualFileTemplateContentContributor = GetRequiredService<VirtualFileTemplateContentContributor>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Localized_Content_By_Culture() |
|||
{ |
|||
(await _virtualFileTemplateContentContributor.GetOrNullAsync( |
|||
new TemplateContentContributorContext(_templateDefinitionManager.Get(TestTemplates.WelcomeEmail), |
|||
(await VirtualFileTemplateContentContributor.GetOrNullAsync( |
|||
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), |
|||
ServiceProvider, |
|||
"en"))) |
|||
.ShouldBe("Welcome {{model.name}} to the abp.io!"); |
|||
.ShouldBe(WelcomeEmailEnglishContent); |
|||
|
|||
(await _virtualFileTemplateContentContributor.GetOrNullAsync( |
|||
new TemplateContentContributorContext(_templateDefinitionManager.Get(TestTemplates.WelcomeEmail), |
|||
(await VirtualFileTemplateContentContributor.GetOrNullAsync( |
|||
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), |
|||
ServiceProvider, |
|||
"tr"))) |
|||
.ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); |
|||
.ShouldBe(WelcomeEmailTurkishContent); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Non_Localized_Template_Content() |
|||
{ |
|||
(await _virtualFileTemplateContentContributor.GetOrNullAsync( |
|||
(await VirtualFileTemplateContentContributor.GetOrNullAsync( |
|||
new TemplateContentContributorContext( |
|||
_templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail), |
|||
TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail), |
|||
ServiceProvider, |
|||
null))) |
|||
.ShouldBe("{{L \"HelloText\" model.name}}, {{L \"HowAreYou\" }}. Please click to the following link to get an email to reset your password!"); |
|||
.ShouldBe(ForgotPasswordEmailEnglishContent); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue