38 changed files with 538 additions and 95 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,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
||||
|
<AssemblyName>LINGYUN.Abp.Notifications.Templating</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.Notifications.Templating</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Core\LINGYUN.Abp.Notifications.Core.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public class AbpNotificationsResolveOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 模板解析提供者列表
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public List<INotificationTemplateResolveContributor> TemplateResolvers { get; } |
||||
|
|
||||
|
public AbpNotificationsResolveOptions() |
||||
|
{ |
||||
|
TemplateResolvers = new List<INotificationTemplateResolveContributor>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
|
||||
|
[DependsOn(typeof(AbpNotificationsCoreModule))] |
||||
|
public class AbpNotificationsTemplatingModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public interface INotificationTemplateResolveContext : IServiceProviderAccessor |
||||
|
{ |
||||
|
NotificationTemplate Template { get; } |
||||
|
|
||||
|
object Model { get; set; } |
||||
|
|
||||
|
bool Handled { get; set; } |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public interface INotificationTemplateResolveContributor |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
|
||||
|
Task ResolveAsync(INotificationTemplateResolveContext context); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
/// <summary>
|
||||
|
/// 通知模板模型解析接口
|
||||
|
/// </summary>
|
||||
|
public interface INotificationTemplateResolver |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 解析模板数据
|
||||
|
/// </summary>
|
||||
|
/// <param name="template"></param>
|
||||
|
/// <returns></returns>
|
||||
|
[NotNull] |
||||
|
Task<NotificationTemplateResolveResult> ResolveAsync(NotificationTemplate template); |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public class NotificationTemplateResolveContext : INotificationTemplateResolveContext |
||||
|
{ |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public NotificationTemplate Template { get; } |
||||
|
|
||||
|
public object Model { get; set; } |
||||
|
|
||||
|
public bool Handled { get; set; } |
||||
|
|
||||
|
public bool HasResolvedModel() |
||||
|
{ |
||||
|
return Handled || Model != null; |
||||
|
} |
||||
|
|
||||
|
public NotificationTemplateResolveContext( |
||||
|
NotificationTemplate template, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Template = template; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public abstract class NotificationTemplateResolveContributorBase : INotificationTemplateResolveContributor |
||||
|
{ |
||||
|
public abstract string Name { get; } |
||||
|
/// <summary>
|
||||
|
/// 实现此接口处理模板数据
|
||||
|
/// </summary>
|
||||
|
/// <param name="context"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public abstract Task ResolveAsync(INotificationTemplateResolveContext context); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public class NotificationTemplateResolveResult |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 模板数据
|
||||
|
/// </summary>
|
||||
|
public object Model { get; set; } |
||||
|
|
||||
|
public List<string> AppliedResolvers { get; } |
||||
|
public NotificationTemplateResolveResult() |
||||
|
{ |
||||
|
AppliedResolvers = new List<string>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public class NotificationTemplateResolver : INotificationTemplateResolver, ITransientDependency |
||||
|
{ |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
private readonly AbpNotificationsResolveOptions _options; |
||||
|
|
||||
|
public NotificationTemplateResolver( |
||||
|
IOptions<AbpNotificationsResolveOptions> options, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
public async virtual Task<NotificationTemplateResolveResult> ResolveAsync(NotificationTemplate template) |
||||
|
{ |
||||
|
var result = new NotificationTemplateResolveResult(); |
||||
|
|
||||
|
using (var serviceScope = _serviceProvider.CreateScope()) |
||||
|
{ |
||||
|
var context = new NotificationTemplateResolveContext(template, serviceScope.ServiceProvider); |
||||
|
|
||||
|
foreach (var resolveContributor in _options.TemplateResolvers) |
||||
|
{ |
||||
|
// TODO: 设定为每一个通知都配置自己的解析提供者?
|
||||
|
/** |
||||
|
if (resolveContributor.Name.Equals(template.Name)) |
||||
|
{ |
||||
|
await resolveContributor.ResolveAsync(context); |
||||
|
} |
||||
|
|
||||
|
if (context.HasResolvedModel()) |
||||
|
{ |
||||
|
result.Model = context.Model; |
||||
|
break; |
||||
|
} |
||||
|
**/ |
||||
|
|
||||
|
await resolveContributor.ResolveAsync(context); |
||||
|
|
||||
|
result.AppliedResolvers.Add(resolveContributor.Name); |
||||
|
|
||||
|
if (context.HasResolvedModel()) |
||||
|
{ |
||||
|
result.Model = context.Model; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net8.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
<Configurations>Debug;Release;</Configurations> |
||||
|
<Platforms>AnyCPU</Platforms> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
<PackageReference Include="xunit" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="Volo.Abp.Json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\realtime-notifications\LINGYUN.Abp.Notifications.Templating\LINGYUN.Abp.Notifications.Templating.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
|
||||
|
public class AbpNotificationsTemplatingTestBase : AbpTestsBase<AbpNotificationsTemplatingTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpNotificationsTemplatingModule), |
||||
|
typeof(AbpJsonModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpNotificationsTemplatingTestModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Newtonsoft.Json.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
internal class NewtownsoftJsonTemplateResolveContributor : INotificationTemplateResolveContributor |
||||
|
{ |
||||
|
public string Name => "ToDynamic"; |
||||
|
|
||||
|
public Task ResolveAsync(INotificationTemplateResolveContext context) |
||||
|
{ |
||||
|
var jsonObject = new JObject(); |
||||
|
foreach (var prop in context.Template.ExtraProperties) |
||||
|
{ |
||||
|
jsonObject.Add(prop.Key, prop.Value.ToString()); |
||||
|
} |
||||
|
context.Model = jsonObject.ToObject<NotificationSimpleModel>(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
|
||||
|
public class NotificationSimpleModel |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
public string Firend { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class NotificationModel |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
public List<NotificationJob> Jobs { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class NotificationJob |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
public NotificationJob() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public NotificationJob(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using LINGYUN.Abp.RealTime; |
||||
|
using Newtonsoft.Json; |
||||
|
using Shouldly; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Json; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
public class NotificationTemplateResolverTests : AbpNotificationsTemplatingTestBase |
||||
|
{ |
||||
|
private IJsonSerializer _jsonSerializer; |
||||
|
public NotificationTemplateResolverTests() |
||||
|
{ |
||||
|
_jsonSerializer = GetRequiredService<IJsonSerializer>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Resolve_Deserialize_To_Object() |
||||
|
{ |
||||
|
var notificationTemplate = new NotificationTemplate( |
||||
|
"Test", |
||||
|
data: new Dictionary<string, object> |
||||
|
{ |
||||
|
{ "Name", "Tom" }, |
||||
|
{ "Jobs", new List<NotificationJob> |
||||
|
{ |
||||
|
new NotificationJob("Catch Jerry"), |
||||
|
new NotificationJob("Hit Pike") |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
var receivedEto = _jsonSerializer.Deserialize<RealTimeEto<NotificationTemplate>>( |
||||
|
_jsonSerializer.Serialize( |
||||
|
new RealTimeEto<NotificationTemplate>(notificationTemplate))); |
||||
|
|
||||
|
var contributor = new ToObjectNotificationTemplateResolveContributor(); |
||||
|
var context = new NotificationTemplateResolveContext(receivedEto.Data, ServiceProvider); |
||||
|
|
||||
|
await contributor.ResolveAsync(context); |
||||
|
|
||||
|
var model = context.Model.ShouldBeOfType<NotificationModel>(); |
||||
|
model.Name.ShouldBe("Tom"); |
||||
|
model.Jobs.Count.ShouldBe(2); |
||||
|
model.Jobs[0].Name.ShouldBe("Catch Jerry"); |
||||
|
model.Jobs[1].Name.ShouldBe("Hit Pike"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Resolve_Deserialize_To_Dynamic() |
||||
|
{ |
||||
|
var notificationTemplate = new NotificationTemplate( |
||||
|
"Test", |
||||
|
data: new Dictionary<string, object> |
||||
|
{ |
||||
|
{ "Name", "Tom" }, |
||||
|
{ "Firend", "Jerry" } |
||||
|
}); |
||||
|
|
||||
|
var receivedEto = _jsonSerializer.Deserialize<RealTimeEto<NotificationTemplate>>( |
||||
|
_jsonSerializer.Serialize( |
||||
|
new RealTimeEto<NotificationTemplate>(notificationTemplate))); |
||||
|
|
||||
|
var contributor = new NewtownsoftJsonTemplateResolveContributor(); |
||||
|
var context = new NotificationTemplateResolveContext(receivedEto.Data, ServiceProvider); |
||||
|
|
||||
|
await contributor.ResolveAsync(context); |
||||
|
|
||||
|
dynamic model = context.Model; |
||||
|
|
||||
|
Assert.Equal(model.Name, "Tom"); |
||||
|
Assert.Equal(model.Firend, "Jerry"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications.Templating; |
||||
|
internal class ToObjectNotificationTemplateResolveContributor : INotificationTemplateResolveContributor |
||||
|
{ |
||||
|
public string Name => "ToObject"; |
||||
|
|
||||
|
public Task ResolveAsync(INotificationTemplateResolveContext context) |
||||
|
{ |
||||
|
var model = new NotificationModel(); |
||||
|
|
||||
|
var nameObj = context.Template.GetProperty(nameof(NotificationModel.Name)); |
||||
|
model.Name = nameObj.ToString(); |
||||
|
|
||||
|
var jobsObj = context.Template.GetProperty(nameof(NotificationModel.Jobs)); |
||||
|
|
||||
|
var jsonSerializer = context.ServiceProvider.GetRequiredService<IJsonSerializer>(); |
||||
|
|
||||
|
model.Jobs = jsonSerializer.Deserialize<List<NotificationJob>>(jobsObj.ToString()); |
||||
|
|
||||
|
context.Model = model; |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue