Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4 #29

Merged
merged 7 commits into from
Mar 21, 2024
Merged

v4 #29

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ["3.1.x", "6.0.x"]
dotnet-version: ["7", "8"]
steps:
- uses: actions/checkout@v4
- name: Setup dotnet ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Display dotnet version
run: dotnet --version
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file
Expand All @@ -38,7 +38,7 @@ jobs:
- name: Test
run: dotnet test --logger "trx;LogFileName=TestResults-${{ matrix.dotnet-version }}.trx"
- name: Upload dotnet test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: dotnet-results-${{ matrix.dotnet-version }}
Expand All @@ -55,14 +55,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3.1.x
6.0.x
7
8
- name: Display dotnet version
run: dotnet --version
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ["3.1.x", "6.0.x"]
dotnet-version: ["7","8"]
steps:
- uses: actions/checkout@v4
- name: Setup dotnet ${{ matrix.dotnet-version }}
Expand Down Expand Up @@ -61,14 +61,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3.1.x
6.0.x
7
8
- name: Display dotnet version
run: dotnet --version
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file
Expand All @@ -94,11 +94,11 @@ jobs:
outputs:
release_created: ${{steps.release.outputs.release_created}}
steps:
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: simple
command: manifest
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

deploy:
runs-on: ubuntu-latest
Expand All @@ -107,14 +107,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3.1.x
6.0.x
7
8
- name: Display dotnet version
run: dotnet --version
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<VersionPrefix>3.1.0</VersionPrefix>
</PropertyGroup>
<PropertyGroup>
<TODAY>$([System.DateTime]::Now.ToString("yyyyMMdd-hhmmss"))</TODAY>
<TODAY>$([System.DateTime]::Now.ToString("yyyyMMdd"))</TODAY>
<InformationalVersion Condition=" '$(SHORT_SHA)' != '' ">$(InformationalVersion)git:$(SHORT_SHA)</InformationalVersion>
</PropertyGroup>
<Choose>
Expand All @@ -25,7 +25,7 @@
<!-- append the build number if it is available -->
<VersionSuffix Condition=" '$(GITHUB_RUN_NUMBER)' != '' ">$(VersionSuffix)$(GITHUB_RUN_NUMBER)</VersionSuffix>
<VersionSuffix Condition=" '$(SHORT_SHA)' != '' ">$(VersionSuffix)-$(SHORT_SHA)</VersionSuffix>
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build$(TODAY)</VersionSuffix>
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build-$(TODAY)</VersionSuffix>
</PropertyGroup>
</Otherwise>
</Choose>
Expand Down
7 changes: 6 additions & 1 deletion Postal.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.Tests", "src\Postal.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.AspNetCore", "src\Postal.AspNetCore\Postal.AspNetCore.csproj", "{95EE9CE4-5C7E-4D69-99C2-CB131C8CE83E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Tests.Integration", "test\Postal.Tests.Integration\Postal.Tests.Integration.csproj", "{6DCE1967-27EC-45B0-AB2E-46315C9DE5E6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.Tests.Integration", "test\Postal.Tests.Integration\Postal.Tests.Integration.csproj", "{6DCE1967-27EC-45B0-AB2E-46315C9DE5E6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FCDB5EF4-20BB-4577-B9DC-7C0F879B0BB9}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
2 changes: 1 addition & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
".": {
"package-name": "Postal.AspNetCore",
"release-type": "simple",
"prerelease": false,
"prerelease": true,
"include-component-in-tag": false,
"extra-files": [
{
Expand Down
30 changes: 15 additions & 15 deletions src/Postal.AspNetCore/Email.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Net.Mail;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Postal.AspNetCore;

namespace Postal
Expand All @@ -17,6 +19,7 @@
/// ViewBag property of a Controller. Any dynamic property access is mapped to the
/// view data dictionary.
/// </summary>
[DataContract]
public class Email : DynamicObject, IViewData
{
/// <summary>Create an Email where the ViewName is derived from the name of the class.</summary>
Expand All @@ -25,9 +28,8 @@
{
Attachments = new List<Attachment>();
ViewName = DeriveViewNameFromClassName();
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
ViewData.Model = this;
ImageEmbedder = new ImageEmbedder();
ViewData = new Dictionary<string, object>();
RequestPath = new RequestPath();
}

/// <summary>
Expand All @@ -38,16 +40,12 @@
{
}

public Email(string viewName, IModelMetadataProvider modelMetadataProvider)
public Email(string viewName, IModelMetadataProvider modelMetadataProvider) : this()
{
if (viewName == null) throw new ArgumentNullException(nameof(viewName));
if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentException("View name cannot be empty.", "viewName");

Attachments = new List<Attachment>();
ViewName = viewName;
ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
ViewData.Model = this;
ImageEmbedder = new ImageEmbedder();
}

/// <summary>
Expand All @@ -63,24 +61,29 @@
/// <summary>
/// The name of the view containing the email template.
/// </summary>
[DataMember]
public string ViewName { get; set; }

/// <summary>
/// The name of the area containing the email template.
/// </summary>
public string AreaName { get; set; }
[DataMember]
public string? AreaName { get; set; }

/// <summary>
/// The view data to pass to the view.
/// </summary>
public ViewDataDictionary ViewData { get; set; }
[DataMember]
public Dictionary<string, object> ViewData { get; set; }

/// <summary>
/// The attachments to send with the email.
/// </summary>
[DataMember]
public List<Attachment> Attachments { get; set; }

internal ImageEmbedder ImageEmbedder { get; private set; }
[DataMember]
public RequestPath RequestPath { get; set; }

/// <summary>
/// Adds an attachment to the email.
Expand Down Expand Up @@ -108,7 +111,7 @@
/// <param name="binder">Provides the name of the view data property.</param>
/// <param name="value">The value to store.</param>
/// <returns>Always returns true.</returns>
public override bool TrySetMember(SetMemberBinder binder, object value)

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 114 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Nullability of type of parameter 'value' doesn't match overridden member (possibly because of nullability attributes).
{
ViewData[binder.Name] = value;
return true;
Expand All @@ -122,7 +125,7 @@
/// <returns>True if the property was found, otherwise false.</returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return ViewData.TryGetValue(binder.Name, out result);

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (7)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test (8)

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Possible null reference assignment.

Check warning on line 128 in src/Postal.AspNetCore/Email.cs

View workflow job for this annotation

GitHub Actions / test-pack

Possible null reference assignment.
}

string DeriveViewNameFromClassName()
Expand All @@ -132,16 +135,13 @@
return viewName;
}

public RequestPath RequestPath { get; set; }
internal HttpContextData HttpContextData { get; private set; }

public void CaptureHttpContext(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
var routeValues = httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues;
HttpContextData = new HttpContextData { Endpoint = endpoint, RouteValues = routeValues };

RequestPath = new RequestPath();
RequestPath.Path = httpContext.Request.Path.ToString();
RequestPath.PathBase = httpContext.Request.PathBase.ToString();
RequestPath.Host = httpContext.Request.Host.ToString();
RequestPath.IsHttps = httpContext.Request.IsHttps;
Expand Down
51 changes: 32 additions & 19 deletions src/Postal.AspNetCore/EmailParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,30 @@ public EmailParser(IEmailViewRender alternativeViewRenderer)
/// <param name="emailViewOutput">The email view output.</param>
/// <param name="email">The <see cref="Email"/> used to generate the output.</param>
/// <returns>A <see cref="MailMessage"/> containing the email headers and content.</returns>
public async Task<MailMessage> ParseAsync(string emailViewOutput, Email email)
public async Task<MailMessage> ParseAsync(string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null)
{
var message = new MailMessage();
await InitializeMailMessageAsync(message, emailViewOutput, email);
await InitializeMailMessageAsync(message, emailViewOutput, email, imageEmbedder);
return message;
}

private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email)
private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null)
{
if (string.IsNullOrWhiteSpace(emailViewOutput))
{
throw new ArgumentNullException(nameof(emailViewOutput));
}
using (var reader = new StringReader(emailViewOutput))
{
await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email));
await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email, imageEmbedder));
AssignCommonHeaders(message, email);
if (message.AlternateViews.Count == 0)
{
var messageBody = reader.ReadToEnd().Trim();
if (email.ImageEmbedder.HasImages)
if (imageEmbedder != null && imageEmbedder.HasImages)
{
var view = AlternateView.CreateAlternateViewFromString(messageBody, new ContentType("text/html"));
email.ImageEmbedder.AddImagesToView(view);
imageEmbedder.AddImagesToView(view);
message.AlternateViews.Add(view);
message.Body = "Plain text not available.";
message.IsBodyHtml = false;
Expand Down Expand Up @@ -112,19 +112,29 @@ private void AssignCommonHeaders(MailMessage message, Email email)
private void AssignCommonHeader<T>(Email email, string header, Action<T> assign)
where T : class
{
object value;
object? value;
if (email.ViewData.TryGetValue(header, out value))
{
var typedValue = value as T;
if (typedValue != null) assign(typedValue);
if (value is T typedValue)
{
assign(typedValue);
return;
}
}
var foundKV = email.ViewData.Where(x => String.Equals(x.Key, header, StringComparison.OrdinalIgnoreCase) && x.Value is T typedValue);
if (foundKV.Any())
{
var val = foundKV.First();
assign((T)val.Value);
return;
}
}

private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email)
private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email, ImageEmbedder? imageEmbedder)
{
if (IsAlternativeViewsHeader(key))
{
foreach (var view in CreateAlternativeViews(value, email))
foreach (var view in CreateAlternativeViews(value, email, imageEmbedder))
{
message.AlternateViews.Add(await view);
}
Expand All @@ -135,17 +145,17 @@ private async Task ProcessHeaderAsync(string key, string value, MailMessage mess
}
}

private IEnumerable<Task<AlternateView>> CreateAlternativeViews(string deliminatedViewNames, Email email)
private IEnumerable<Task<AlternateView>> CreateAlternativeViews(string deliminatedViewNames, Email email, ImageEmbedder? imageEmbedder)
{
var viewNames = deliminatedViewNames.Split(new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
return viewNames.Select(v => CreateAlternativeView(email, v)).ToList();
return viewNames.Select(v => CreateAlternativeView(email, v, imageEmbedder)).ToList();
}

private async Task<AlternateView> CreateAlternativeView(Email email, string alternativeViewName)
private async Task<AlternateView> CreateAlternativeView(Email email, string alternativeViewName, ImageEmbedder? imageEmbedder)
{
var fullViewName = GetAlternativeViewName(email, alternativeViewName);
var output = await alternativeViewRenderer.RenderAsync(email, fullViewName);
string contentType;
var output = await alternativeViewRenderer.RenderAsync(email, fullViewName, imageEmbedder);
string? contentType;
string body;
using (var reader = new StringReader(output))
{
Expand Down Expand Up @@ -179,7 +189,10 @@ private async Task<AlternateView> CreateAlternativeView(Email email, string alte
// A different charset can be specified in the Content-Type header.
// e.g. Content-Type: text/html; charset=utf-8
}
email.ImageEmbedder.AddImagesToView(alternativeView);
if (imageEmbedder != null)
{
imageEmbedder.AddImagesToView(alternativeView);
}
return alternativeView;
}

Expand All @@ -206,9 +219,9 @@ private MemoryStream CreateStreamOfBody(string body)
return stream;
}

private string ParseHeadersForContentType(StringReader reader)
private string? ParseHeadersForContentType(StringReader reader)
{
string contentType = null;
string? contentType = null;
ParserUtils.ParseHeaders(reader, (key, value) =>
{
if (key.Equals("content-type", StringComparison.OrdinalIgnoreCase))
Expand Down
Loading
Loading