From 0f117a032b71f2f70227844f13c0b1a72704f58e Mon Sep 17 00:00:00 2001 From: Hesam Asnaashari <46031836+ilhesam@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:10:41 +0330 Subject: [PATCH] feat - implement content template engine --- .../Controllers/ContentTemplatesController.cs | 47 +++++++++++++++++++ .../Identity.MongoDB.API.csproj | 3 ++ src/Examples/Identity.MongoDB.API/Program.cs | 7 +++ .../Entities/ContentTemplate.cs | 8 ++++ .../Interfaces/Builder.cs | 8 ++++ .../Interfaces/Renderer.cs | 6 +++ .../Interfaces/Repository.cs | 8 ++++ .../Interfaces/Service.cs | 11 +++++ .../Models/RenderedContent.cs | 7 +++ ...Core.TemplateRendering.Abstractions.csproj | 15 ++++++ .../BuilderExtensions.cs | 14 ++++++ .../Renderer.cs | 21 +++++++++ ...emplateRendering.Renderers.Mustache.csproj | 19 ++++++++ .../BuilderExtensions.cs | 16 +++++++ .../Repository.cs | 25 ++++++++++ ...plateRendering.Repositories.MongoDB.csproj | 14 ++++++ .../ServiceCollectionBuilder.cs | 35 ++++++++++++++ .../ServiceCollectionExtensions.cs | 18 +++++++ .../Services/ContentTemplateService.cs | 41 ++++++++++++++++ .../uBeac.Core.TemplateRendering.csproj | 18 +++++++ src/uBeac.Core.sln | 30 ++++++++++++ 21 files changed, 371 insertions(+) create mode 100644 src/Examples/Identity.MongoDB.API/Controllers/ContentTemplatesController.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Entities/ContentTemplate.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Builder.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Renderer.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Repository.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Service.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Models/RenderedContent.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/uBeac.Core.TemplateRendering.Abstractions.csproj create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/BuilderExtensions.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/Renderer.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/uBeac.Core.TemplateRendering.Renderers.Mustache.csproj create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/BuilderExtensions.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/Repository.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/uBeac.Core.TemplateRendering.Repositories.MongoDB.csproj create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionBuilder.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionExtensions.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering/Services/ContentTemplateService.cs create mode 100644 src/TemplateRendering/uBeac.Core.TemplateRendering/uBeac.Core.TemplateRendering.csproj diff --git a/src/Examples/Identity.MongoDB.API/Controllers/ContentTemplatesController.cs b/src/Examples/Identity.MongoDB.API/Controllers/ContentTemplatesController.cs new file mode 100644 index 0000000..e71b58f --- /dev/null +++ b/src/Examples/Identity.MongoDB.API/Controllers/ContentTemplatesController.cs @@ -0,0 +1,47 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using uBeac.TemplateRendering; +using uBeac.Web; +using IResult = uBeac.IResult; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Identity.MongoDB.API.Controllers; + +public class ContentTemplatesController : BaseController +{ + protected readonly IContentTemplateService ContentTemplateService; + + public ContentTemplatesController(IContentTemplateService contentTemplateService) + { + ContentTemplateService = contentTemplateService; + } + + [HttpPost] + public async Task Test(string siteName = "uBeac", string userName = "Hesam", CancellationToken cancellationToken = default) + { + var randomNumber = new Random().Next(); + + var templateKey = $"sign-up-email-{randomNumber}"; + + var template = new ContentTemplate + { + UniqueKey = templateKey, + Subject = "Welcome to {{SiteName}}", + Body = "Hello {{UserName}}, Your account has been created successfully!" + }; + + await ContentTemplateService.Create(template, cancellationToken); + + var model = new SignUpContent { SiteName = siteName, UserName = userName }; + var result = await ContentTemplateService.Render(templateKey, model, cancellationToken); + + return result.ToResult(); + } + + private class SignUpContent + { + public string SiteName { get; set; } + public string UserName { get; set; } + } +} diff --git a/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj b/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj index 7139a45..f49eac6 100644 --- a/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj +++ b/src/Examples/Identity.MongoDB.API/Identity.MongoDB.API.csproj @@ -17,6 +17,9 @@ + + + diff --git a/src/Examples/Identity.MongoDB.API/Program.cs b/src/Examples/Identity.MongoDB.API/Program.cs index 133eca4..d6fee1e 100644 --- a/src/Examples/Identity.MongoDB.API/Program.cs +++ b/src/Examples/Identity.MongoDB.API/Program.cs @@ -43,6 +43,13 @@ builder.Services.AddMongo("HistoryConnection"); builder.Services.AddHistory().For(); +// Adding template rendering +builder.Services.AddTemplateRendering(templateRendering => +{ + templateRendering.UseMongoDB(); // It's not required, If you don't want storing your templates in the database, remove it! + templateRendering.UseMustacheRenderer(); +}); + // Adding CORS var corsPolicyOptions = builder.Configuration.GetSection("CorsPolicy"); builder.Services.AddCorsPolicy(corsPolicyOptions); diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Entities/ContentTemplate.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Entities/ContentTemplate.cs new file mode 100644 index 0000000..8df48a1 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Entities/ContentTemplate.cs @@ -0,0 +1,8 @@ +namespace uBeac.TemplateRendering; + +public class ContentTemplate : Entity +{ + public string UniqueKey { get; set; } // "sign-up-email" + public string Subject { get; set; } // "Welcome to {{SiteName}}" + public string Body { get; set; } // "Hello {{Name}}, Your account has been created successfully!" +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Builder.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Builder.cs new file mode 100644 index 0000000..e4ebeea --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Builder.cs @@ -0,0 +1,8 @@ +namespace uBeac.TemplateRendering; + +public interface ITemplateRenderingBuilder +{ + ITemplateRenderingBuilder SetRepository(Type repositoryType); + ITemplateRenderingBuilder SetService(Type serviceType); + ITemplateRenderingBuilder SetRenderer(Type rendererType); +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Renderer.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Renderer.cs new file mode 100644 index 0000000..9bfa84b --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Renderer.cs @@ -0,0 +1,6 @@ +namespace uBeac.TemplateRendering; + +public interface ITemplateRenderer +{ + Task Render(string template, object model); +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Repository.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Repository.cs new file mode 100644 index 0000000..7e5a681 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Repository.cs @@ -0,0 +1,8 @@ +using uBeac.Repositories; + +namespace uBeac.TemplateRendering; + +public interface IContentTemplateRepository : IEntityRepository +{ + Task GetByUniqueKey(string uniqueKey, CancellationToken cancellationToken = default); +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Service.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Service.cs new file mode 100644 index 0000000..7336c9f --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Interfaces/Service.cs @@ -0,0 +1,11 @@ +using uBeac.Services; + +namespace uBeac.TemplateRendering; + +public interface IContentTemplateService : IEntityService +{ + Task GetByUniqueKey(string uniqueKey, CancellationToken cancellationToken = default); + + Task Render(string templateKey, object model, CancellationToken cancellationToken = default); + Task Render(ContentTemplate template, object model, CancellationToken cancellationToken = default); +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Models/RenderedContent.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Models/RenderedContent.cs new file mode 100644 index 0000000..7e5ee41 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/Models/RenderedContent.cs @@ -0,0 +1,7 @@ +namespace uBeac.TemplateRendering; + +public class RenderedContent +{ + public string Subject { get; set; } + public string Body { get; set; } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/uBeac.Core.TemplateRendering.Abstractions.csproj b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/uBeac.Core.TemplateRendering.Abstractions.csproj new file mode 100644 index 0000000..73c4a21 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Abstractions/uBeac.Core.TemplateRendering.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + disable + + + + + + + + + diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/BuilderExtensions.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/BuilderExtensions.cs new file mode 100644 index 0000000..0c14a14 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/BuilderExtensions.cs @@ -0,0 +1,14 @@ +using uBeac.TemplateRendering; +using uBeac.TemplateRendering.Renderers.Mustache; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class BuilderExtensions +{ + public static ITemplateRenderingBuilder UseMustacheRenderer(this ITemplateRenderingBuilder builder) + { + builder.SetRenderer(typeof(MustacheTemplateRenderer)); + + return builder; + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/Renderer.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/Renderer.cs new file mode 100644 index 0000000..215aec0 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/Renderer.cs @@ -0,0 +1,21 @@ +using Stubble.Core.Builders; +using Stubble.Extensions.JsonNet; + +namespace uBeac.TemplateRendering.Renderers.Mustache; + +public class MustacheTemplateRenderer : ITemplateRenderer +{ + public async Task Render(string template, object model) + { + var stubble = new StubbleBuilder() + .Configure(settings => + { + settings.AddJsonNet(); + settings.SetIgnoreCaseOnKeyLookup(true); + settings.SetMaxRecursionDepth(512); + }) + .Build(); + + return await stubble.RenderAsync(template, model); + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/uBeac.Core.TemplateRendering.Renderers.Mustache.csproj b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/uBeac.Core.TemplateRendering.Renderers.Mustache.csproj new file mode 100644 index 0000000..761128c --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Renderers.Mustache/uBeac.Core.TemplateRendering.Renderers.Mustache.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + disable + + + + + + + + + + + + + diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/BuilderExtensions.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/BuilderExtensions.cs new file mode 100644 index 0000000..7cb40e8 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/BuilderExtensions.cs @@ -0,0 +1,16 @@ +using uBeac.Repositories.MongoDB; +using uBeac.TemplateRendering; +using uBeac.TemplateRendering.Repositories.MongoDB; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class BuilderExtensions +{ + public static ITemplateRenderingBuilder UseMongoDB(this ITemplateRenderingBuilder builder) + where TContext : IMongoDBContext + { + builder.SetRepository(typeof(MongoDBContentTemplateRepository)); + + return builder; + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/Repository.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/Repository.cs new file mode 100644 index 0000000..52b9ead --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/Repository.cs @@ -0,0 +1,25 @@ +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using uBeac.Repositories.History; +using uBeac.Repositories.MongoDB; + +namespace uBeac.TemplateRendering.Repositories.MongoDB; + +public interface IMongoDBContentTemplateRepository : IContentTemplateRepository +{ +} + +public class MongoDBContentTemplateRepository : MongoEntityRepository, IMongoDBContentTemplateRepository + where TContext : IMongoDBContext +{ + public MongoDBContentTemplateRepository(TContext mongoDbContext, IApplicationContext applicationContext, IHistoryManager history) : base(mongoDbContext, applicationContext, history) + { + } + + public async Task GetByUniqueKey(string uniqueKey, CancellationToken cancellationToken = default) + { + return await Collection + .AsQueryable() + .SingleAsync(x => x.UniqueKey == uniqueKey, cancellationToken); + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/uBeac.Core.TemplateRendering.Repositories.MongoDB.csproj b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/uBeac.Core.TemplateRendering.Repositories.MongoDB.csproj new file mode 100644 index 0000000..f077abd --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering.Repositories.MongoDB/uBeac.Core.TemplateRendering.Repositories.MongoDB.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + disable + + + + + + + + diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionBuilder.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionBuilder.cs new file mode 100644 index 0000000..2762704 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionBuilder.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace uBeac.TemplateRendering; + +public class ServiceCollectionTemplateRenderingBuilder : ITemplateRenderingBuilder +{ + protected readonly IServiceCollection Services; + + public ServiceCollectionTemplateRenderingBuilder(IServiceCollection services) + { + Services = services; + } + + public ITemplateRenderingBuilder SetRepository(Type repositoryType) + { + Services.TryAddScoped(typeof(IContentTemplateRepository), repositoryType); + + return this; + } + + public ITemplateRenderingBuilder SetService(Type serviceType) + { + Services.TryAddScoped(typeof(IContentTemplateService), serviceType); + + return this; + } + + public ITemplateRenderingBuilder SetRenderer(Type rendererType) + { + Services.TryAddScoped(typeof(ITemplateRenderer), rendererType); + + return this; + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionExtensions.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..8c4bb7d --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering/ServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using uBeac.TemplateRendering; +using uBeac.TemplateRendering.Services; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddTemplateRendering(this IServiceCollection services, Action builder) + { + var serviceCollectionBuilder = new ServiceCollectionTemplateRenderingBuilder(services); + + builder(serviceCollectionBuilder); + + serviceCollectionBuilder.SetService(typeof(ContentTemplateService)); + + return services; + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering/Services/ContentTemplateService.cs b/src/TemplateRendering/uBeac.Core.TemplateRendering/Services/ContentTemplateService.cs new file mode 100644 index 0000000..92bbe6c --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering/Services/ContentTemplateService.cs @@ -0,0 +1,41 @@ +using uBeac.Services; + +namespace uBeac.TemplateRendering.Services; + +public class ContentTemplateService : EntityService, IContentTemplateService +{ + protected new readonly IContentTemplateRepository Repository; + protected readonly ITemplateRenderer Renderer; + + public ContentTemplateService(IContentTemplateRepository repository, ITemplateRenderer renderer) : base(repository) + { + Repository = repository; + Renderer = renderer; + } + + public async Task GetByUniqueKey(string uniqueKey, CancellationToken cancellationToken = default) + { + return await Repository.GetByUniqueKey(uniqueKey, cancellationToken); + } + + public async Task Render(string templateKey, object model, CancellationToken cancellationToken = default) + { + var template = await Repository.GetByUniqueKey(templateKey, cancellationToken); + + return await Render(template, model, cancellationToken); + } + + public async Task Render(ContentTemplate template, object model, CancellationToken cancellationToken = default) + { + var result = new RenderedContent(); + + if (!string.IsNullOrWhiteSpace(template.Subject)) + { + result.Subject = await Renderer.Render(template.Subject, model); + } + + result.Body = await Renderer.Render(template.Body, model); + + return result; + } +} diff --git a/src/TemplateRendering/uBeac.Core.TemplateRendering/uBeac.Core.TemplateRendering.csproj b/src/TemplateRendering/uBeac.Core.TemplateRendering/uBeac.Core.TemplateRendering.csproj new file mode 100644 index 0000000..113f119 --- /dev/null +++ b/src/TemplateRendering/uBeac.Core.TemplateRendering/uBeac.Core.TemplateRendering.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/src/uBeac.Core.sln b/src/uBeac.Core.sln index 661b9b2..32313c9 100644 --- a/src/uBeac.Core.sln +++ b/src/uBeac.Core.sln @@ -67,6 +67,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "uBeac.Core.Identity.Jwt", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{6D83110C-1361-45E1-A638-6BC37F0B968F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TemplateRendering", "TemplateRendering", "{0D02C8A3-02A2-408A-B827-AD92F7F6A1C5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.TemplateRendering.Abstractions", "TemplateRendering\uBeac.Core.TemplateRendering.Abstractions\uBeac.Core.TemplateRendering.Abstractions.csproj", "{D7BD0885-24AA-4C30-B151-5812281D4777}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.TemplateRendering.Repositories.MongoDB", "TemplateRendering\uBeac.Core.TemplateRendering.Repositories.MongoDB\uBeac.Core.TemplateRendering.Repositories.MongoDB.csproj", "{DDEC3630-671B-4606-90D3-EE776288A7D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.TemplateRendering.Renderers.Mustache", "TemplateRendering\uBeac.Core.TemplateRendering.Renderers.Mustache\uBeac.Core.TemplateRendering.Renderers.Mustache.csproj", "{BAE21325-6BA2-4BDD-9FBB-9AA347E34710}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uBeac.Core.TemplateRendering", "TemplateRendering\uBeac.Core.TemplateRendering\uBeac.Core.TemplateRendering.csproj", "{5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -169,6 +179,22 @@ Global {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0}.Release|Any CPU.Build.0 = Release|Any CPU + {D7BD0885-24AA-4C30-B151-5812281D4777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7BD0885-24AA-4C30-B151-5812281D4777}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7BD0885-24AA-4C30-B151-5812281D4777}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7BD0885-24AA-4C30-B151-5812281D4777}.Release|Any CPU.Build.0 = Release|Any CPU + {DDEC3630-671B-4606-90D3-EE776288A7D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDEC3630-671B-4606-90D3-EE776288A7D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDEC3630-671B-4606-90D3-EE776288A7D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDEC3630-671B-4606-90D3-EE776288A7D9}.Release|Any CPU.Build.0 = Release|Any CPU + {BAE21325-6BA2-4BDD-9FBB-9AA347E34710}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAE21325-6BA2-4BDD-9FBB-9AA347E34710}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAE21325-6BA2-4BDD-9FBB-9AA347E34710}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAE21325-6BA2-4BDD-9FBB-9AA347E34710}.Release|Any CPU.Build.0 = Release|Any CPU + {5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -198,6 +224,10 @@ Global {9D7D2782-2067-4B55-990B-ABF03939C1B3} = {EA4A3C91-FB93-49E8-8DF9-8CE86C150F71} {FAAE32CA-48F3-4F26-BF22-39DDDDF57770} = {6D83110C-1361-45E1-A638-6BC37F0B968F} {6BD1B9F9-5A0F-4A43-A657-F8B31896EDE0} = {EA4A3C91-FB93-49E8-8DF9-8CE86C150F71} + {D7BD0885-24AA-4C30-B151-5812281D4777} = {0D02C8A3-02A2-408A-B827-AD92F7F6A1C5} + {DDEC3630-671B-4606-90D3-EE776288A7D9} = {0D02C8A3-02A2-408A-B827-AD92F7F6A1C5} + {BAE21325-6BA2-4BDD-9FBB-9AA347E34710} = {0D02C8A3-02A2-408A-B827-AD92F7F6A1C5} + {5C97C485-D6CE-44D2-9ACB-6DF63DDDEE1F} = {0D02C8A3-02A2-408A-B827-AD92F7F6A1C5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {247F9BF0-18BB-4A57-BB3E-2870DDAEF0D1}