Skip to content

Commit

Permalink
feat: added easier setup and use of embedded templates (#16)
Browse files Browse the repository at this point in the history
* Added simplified configuration for embedded templates
  • Loading branch information
JohnCampionJr authored Nov 21, 2023
1 parent 7fdd5cd commit f1e494c
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v3.5.0

- Added simplified configuration to setup and use embedded templates with and without the LiquidRenderer.

## v3.4.0

- Added MailPace sender - thanks [@maartenba](https://github.com/maartenba)
Expand Down
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# FluentEmail - All in one email sender for .NET and .NET Core

The easiest way to send email from .NET and .NET Core. Use Razor for email templates and send using SendGrid, MailGun, SMTP and more.
The easiest way to send email from .NET and .NET Core. Use Razor or Liquid for email templates and send using SendGrid, MailGun, MailKit, SMTP and more.

Forked from original by **[@lukencode](https://github.com/lukencode/fluentemail)**

Expand Down Expand Up @@ -130,6 +130,44 @@ var email = Email
.UsingTemplate(template, new ViewModel { Name = "Luke", Compliment = "Awesome" });
```

## Embedded Templates

There is a set of extensions in `EmbeddedTemplates` that allows for use of embedded templates without specifying the assembly and the path every time.

```csharp
EmbeddedTemplates.Configure(Assembly.GetExecutingAssembly(), "FluentEmail.Core.Tests");
var email = Email
.From(fromEmail)
.To(toEmail)
.Subject(subject)
.UsingTemplateFromEmbedded("templatename.liquid", new ViewModel { Name = "Luke", Compliment = "Awesome" });
```

## Embedded Templates with Liquid Renderer

Because the Liquid templates can also be configured with an embedded provider, there are builder extensions that will configure both the embedded file provider for layouts and the `EmbeddedTemplates` extensions.

There is a default of the executing assembly with Templates in `EmailTemplates`

```csharp
builder.Services.AddFluentEmail("[email protected]")
.AddLiquidRendererWithEmbedded(Assembly.GetExecutingAssembly(), "AssemblyName.EmailTemplates")

// These are the same
builder.Services.AddFluentEmail("[email protected]")
.AddLiquidRendererWithEmbedded()
```

## How to set all templates as embedded in the `csproj` file

If you want all templates in a folder to automatically be embedded, use the following in your `csproj` file.

```xml
<ItemGroup>
<EmbeddedResource Include="EmailTemplates/**/*.liquid" />
</ItemGroup>
```

## Sending Emails

```csharp
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Version>3.4.0</Version>
<Version>3.5.0</Version>


<PublishRepositoryUrl>true</PublishRepositoryUrl>
Expand Down
13 changes: 7 additions & 6 deletions src/FluentEmail.Core/EmbeddedResourceHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Reflection;

namespace FluentEmail.Core
Expand All @@ -7,14 +8,14 @@ internal static class EmbeddedResourceHelper
{
internal static string GetResourceAsString(Assembly assembly, string path)
{
string result;

using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
using var stream = assembly.GetManifestResourceStream(path);
if (stream is null)
{
result = reader.ReadToEnd();
throw new Exception($"{path} was not found in embedded resources.");
}

using var reader = new StreamReader(stream);
var result = reader.ReadToEnd();
return result;
}
}
Expand Down
60 changes: 60 additions & 0 deletions src/FluentEmail.Core/EmbeddedTemplates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Reflection;

namespace FluentEmail.Core;

public static class EmbeddedTemplates
{
private static Assembly _assembly;
private static string _rootPath;

public static void Configure(Assembly assembly, string rootPath)
{
_assembly = assembly;
_rootPath = rootPath;
}

/// <summary>
/// Adds template to email from previously configured default embedded resource
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path">Path the the embedded resource eg [YourResourceFolder].[YourFilename.txt]. Will be appended to configured root path</param>
/// <param name="model">Model for the template</param>
/// <param name="isHtml">True if Body is HTML (default), false for plain text</param>
/// <returns></returns>
public static IFluentEmail UsingTemplateFromEmbedded<T>(this IFluentEmail email, string path, T model, bool isHtml = true)
{
if (_assembly is null)
{
throw new Exception("FluentEmailEmbeddedExtensions.Configure must be called with default assembly and root path");
}

var root = _rootPath;
if (!string.IsNullOrEmpty(root)) root += ".";
var template = EmbeddedResourceHelper.GetResourceAsString(_assembly, $"{root}{path}");
var result = email.Renderer.Parse(template, model, isHtml);
email.Data.IsHtml = isHtml;
email.Data.Body = result;

return email;
}

/// <summary>
/// Adds alternative plaintext template to email from previously configured embedded resource
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path">Path the the embedded resource eg [YourResourceFolder].[YourFilename.txt]. Will be appended to configured root path</param>
/// <param name="model">Model for the template</param>
/// <returns></returns>
public static IFluentEmail PlaintextAlternativeUsingTemplateFromEmbedded<T>(this IFluentEmail email, string path, T model)
{
var root = _rootPath;
if (!string.IsNullOrEmpty(root)) root += ".";
var template = EmbeddedResourceHelper.GetResourceAsString(_assembly, $"{root}{path}");
var result = email.Renderer.Parse(template, model, false);
email.Data.PlaintextAlternativeBody = result;

return email;
}

}
1 change: 1 addition & 0 deletions src/Renderers/FluentEmail.Liquid/FluentEmail.Liquid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="Fluid.Core" Version="2.3.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.13" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;

using System.Reflection;
using FluentEmail.Core;
using FluentEmail.Core.Interfaces;
using FluentEmail.Liquid;

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
Expand All @@ -23,5 +25,50 @@ public static FluentEmailServicesBuilder AddLiquidRenderer(
builder.Services.TryAddSingleton<ITemplateRenderer, LiquidRenderer>();
return builder;
}

public static FluentEmailServicesBuilder AddLiquidRendererWithEmbedded(
this FluentEmailServicesBuilder builder,
Action<LiquidRendererOptions>? configure = null)
{
var assembly = Assembly.GetExecutingAssembly();
var name = assembly.GetName().Name;
return AddLiquidRendererWithEmbedded(builder, assembly, $"{name}.EmailTemplates", configure);
}

public static FluentEmailServicesBuilder AddLiquidRendererWithEmbedded(
this FluentEmailServicesBuilder builder,
Assembly assembly,
Action<LiquidRendererOptions>? configure = null)
{
var name = assembly.GetName().Name;
return AddLiquidRendererWithEmbedded(builder, assembly, $"{name}.EmailTemplates", configure);
}

public static FluentEmailServicesBuilder AddLiquidRendererWithEmbedded(
this FluentEmailServicesBuilder builder,
string rootPath,
Action<LiquidRendererOptions>? configure = null)
{
var assembly = Assembly.GetExecutingAssembly();
var name = assembly.GetName().Name;
if (!string.IsNullOrEmpty(rootPath)) name += ".";
return AddLiquidRendererWithEmbedded(builder, assembly, $"{name}{rootPath}", configure);
}

public static FluentEmailServicesBuilder AddLiquidRendererWithEmbedded(
this FluentEmailServicesBuilder builder,
Assembly assembly,
string rootNamespace,
Action<LiquidRendererOptions>? configure = null)
{
builder.AddLiquidRenderer(options =>
{
options.FileProvider = new EmbeddedFileProvider(assembly, rootNamespace);
configure?.Invoke(options);
});
EmbeddedTemplates.Configure(assembly, rootNamespace);
return builder;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
yo email ##Test##
1 change: 1 addition & 0 deletions test/FluentEmail.Core.Tests/FluentEmail.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<None Update="*.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<EmbeddedResource Include="EmailTemplates\test-embedded.txt" />
<EmbeddedResource Include="test-embedded.txt" />
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<None Update="logotest.png">
Expand Down
26 changes: 26 additions & 0 deletions test/FluentEmail.Core.Tests/TemplateEmailTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,32 @@ public void Using_Template_From_Embedded_Resource()

Assert.AreEqual("yo email EMBEDDEDTEST", email.Data.Body);
}

[Test]
public void Using_Template_From_Root_Configured_Embedded_Resource()
{
EmbeddedTemplates.Configure(Assembly.GetExecutingAssembly(), "FluentEmail.Core.Tests");
var email = Email
.From(fromEmail)
.To(toEmail)
.Subject(subject)
.UsingTemplateFromEmbedded("test-embedded.txt", new { Test = "EMBEDDEDTEST" });

Assert.AreEqual("yo email EMBEDDEDTEST", email.Data.Body);
}

[Test]
public void Using_Template_From_Configured_Embedded_Resource()
{
EmbeddedTemplates.Configure(Assembly.GetExecutingAssembly(), "FluentEmail.Core.Tests.EmailTemplates");
var email = Email
.From(fromEmail)
.To(toEmail)
.Subject(subject)
.UsingTemplateFromEmbedded("test-embedded.txt", new { Test = "EMBEDDEDTEST" });

Assert.AreEqual("yo email EMBEDDEDTEST", email.Data.Body);
}

[Test]
public void New_Anonymous_Model_Template_From_File_Matches()
Expand Down

0 comments on commit f1e494c

Please sign in to comment.