Skip to content

Commit 058841d

Browse files
authored
Merge pull request #29 from hermanho/feat/v4
v4
2 parents fbdc277 + c21e5a6 commit 058841d

37 files changed

+539
-277
lines changed

.github/workflows/pull-request.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
dotnet-version: ["3.1.x", "6.0.x"]
18+
dotnet-version: ["7", "8"]
1919
steps:
2020
- uses: actions/checkout@v4
2121
- name: Setup dotnet ${{ matrix.dotnet-version }}
22-
uses: actions/setup-dotnet@v3
22+
uses: actions/setup-dotnet@v4
2323
with:
2424
dotnet-version: ${{ matrix.dotnet-version }}
2525
- name: Display dotnet version
2626
run: dotnet --version
27-
- uses: actions/cache@v3
27+
- uses: actions/cache@v4
2828
with:
2929
path: ~/.nuget/packages
3030
# Look to see if there is a cache hit for the corresponding requirements file
@@ -38,7 +38,7 @@ jobs:
3838
- name: Test
3939
run: dotnet test --logger "trx;LogFileName=TestResults-${{ matrix.dotnet-version }}.trx"
4040
- name: Upload dotnet test results
41-
uses: actions/upload-artifact@v3
41+
uses: actions/upload-artifact@v4
4242
if: always()
4343
with:
4444
name: dotnet-results-${{ matrix.dotnet-version }}
@@ -55,14 +55,14 @@ jobs:
5555
steps:
5656
- uses: actions/checkout@v4
5757
- name: Setup dotnet
58-
uses: actions/setup-dotnet@v3
58+
uses: actions/setup-dotnet@v4
5959
with:
6060
dotnet-version: |
61-
3.1.x
62-
6.0.x
61+
7
62+
8
6363
- name: Display dotnet version
6464
run: dotnet --version
65-
- uses: actions/cache@v3
65+
- uses: actions/cache@v4
6666
with:
6767
path: ~/.nuget/packages
6868
# Look to see if there is a cache hit for the corresponding requirements file

.github/workflows/release-please.yml

+12-12
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
runs-on: ubuntu-latest
2222
strategy:
2323
matrix:
24-
dotnet-version: ["3.1.x", "6.0.x"]
24+
dotnet-version: ["7","8"]
2525
steps:
2626
- uses: actions/checkout@v4
2727
- name: Setup dotnet ${{ matrix.dotnet-version }}
@@ -61,14 +61,14 @@ jobs:
6161
steps:
6262
- uses: actions/checkout@v4
6363
- name: Setup dotnet
64-
uses: actions/setup-dotnet@v3
64+
uses: actions/setup-dotnet@v4
6565
with:
6666
dotnet-version: |
67-
3.1.x
68-
6.0.x
67+
7
68+
8
6969
- name: Display dotnet version
7070
run: dotnet --version
71-
- uses: actions/cache@v3
71+
- uses: actions/cache@v4
7272
with:
7373
path: ~/.nuget/packages
7474
# Look to see if there is a cache hit for the corresponding requirements file
@@ -94,11 +94,11 @@ jobs:
9494
outputs:
9595
release_created: ${{steps.release.outputs.release_created}}
9696
steps:
97-
- uses: google-github-actions/release-please-action@v3
97+
- uses: google-github-actions/release-please-action@v4
9898
id: release
9999
with:
100-
release-type: simple
101-
command: manifest
100+
config-file: release-please-config.json
101+
manifest-file: .release-please-manifest.json
102102

103103
deploy:
104104
runs-on: ubuntu-latest
@@ -107,14 +107,14 @@ jobs:
107107
steps:
108108
- uses: actions/checkout@v4
109109
- name: Setup dotnet
110-
uses: actions/setup-dotnet@v3
110+
uses: actions/setup-dotnet@v4
111111
with:
112112
dotnet-version: |
113-
3.1.x
114-
6.0.x
113+
7
114+
8
115115
- name: Display dotnet version
116116
run: dotnet --version
117-
- uses: actions/cache@v3
117+
- uses: actions/cache@v4
118118
with:
119119
path: ~/.nuget/packages
120120
# Look to see if there is a cache hit for the corresponding requirements file

Directory.Build.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<VersionPrefix>3.1.0</VersionPrefix>
55
</PropertyGroup>
66
<PropertyGroup>
7-
<TODAY>$([System.DateTime]::Now.ToString("yyyyMMdd-hhmmss"))</TODAY>
7+
<TODAY>$([System.DateTime]::Now.ToString("yyyyMMdd"))</TODAY>
88
<InformationalVersion Condition=" '$(SHORT_SHA)' != '' ">$(InformationalVersion)git:$(SHORT_SHA)</InformationalVersion>
99
</PropertyGroup>
1010
<Choose>
@@ -25,7 +25,7 @@
2525
<!-- append the build number if it is available -->
2626
<VersionSuffix Condition=" '$(GITHUB_RUN_NUMBER)' != '' ">$(VersionSuffix)$(GITHUB_RUN_NUMBER)</VersionSuffix>
2727
<VersionSuffix Condition=" '$(SHORT_SHA)' != '' ">$(VersionSuffix)-$(SHORT_SHA)</VersionSuffix>
28-
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build$(TODAY)</VersionSuffix>
28+
<VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build-$(TODAY)</VersionSuffix>
2929
</PropertyGroup>
3030
</Otherwise>
3131
</Choose>

Postal.sln

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.Tests", "src\Postal.
66
EndProject
77
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.AspNetCore", "src\Postal.AspNetCore\Postal.AspNetCore.csproj", "{95EE9CE4-5C7E-4D69-99C2-CB131C8CE83E}"
88
EndProject
9-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Tests.Integration", "test\Postal.Tests.Integration\Postal.Tests.Integration.csproj", "{6DCE1967-27EC-45B0-AB2E-46315C9DE5E6}"
9+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Postal.Tests.Integration", "test\Postal.Tests.Integration\Postal.Tests.Integration.csproj", "{6DCE1967-27EC-45B0-AB2E-46315C9DE5E6}"
10+
EndProject
11+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FCDB5EF4-20BB-4577-B9DC-7C0F879B0BB9}"
12+
ProjectSection(SolutionItems) = preProject
13+
Directory.Build.props = Directory.Build.props
14+
EndProjectSection
1015
EndProject
1116
Global
1217
GlobalSection(SolutionConfigurationPlatforms) = preSolution

release-please-config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
".": {
44
"package-name": "Postal.AspNetCore",
55
"release-type": "simple",
6-
"prerelease": false,
6+
"prerelease": true,
77
"include-component-in-tag": false,
88
"extra-files": [
99
{

src/Postal.AspNetCore/Email.cs

+15-15
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
using System.Collections.Generic;
33
using System.Dynamic;
44
using System.Net.Mail;
5+
using System.Runtime.Serialization;
56
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Http;
78
using Microsoft.AspNetCore.Http.Features;
89
using Microsoft.AspNetCore.Mvc.ModelBinding;
910
using Microsoft.AspNetCore.Mvc.ViewFeatures;
11+
using Microsoft.AspNetCore.Routing;
1012
using Postal.AspNetCore;
1113

1214
namespace Postal
@@ -17,6 +19,7 @@ namespace Postal
1719
/// ViewBag property of a Controller. Any dynamic property access is mapped to the
1820
/// view data dictionary.
1921
/// </summary>
22+
[DataContract]
2023
public class Email : DynamicObject, IViewData
2124
{
2225
/// <summary>Create an Email where the ViewName is derived from the name of the class.</summary>
@@ -25,9 +28,8 @@ protected Email()
2528
{
2629
Attachments = new List<Attachment>();
2730
ViewName = DeriveViewNameFromClassName();
28-
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
29-
ViewData.Model = this;
30-
ImageEmbedder = new ImageEmbedder();
31+
ViewData = new Dictionary<string, object>();
32+
RequestPath = new RequestPath();
3133
}
3234

3335
/// <summary>
@@ -38,16 +40,12 @@ protected Email()
3840
{
3941
}
4042

41-
public Email(string viewName, IModelMetadataProvider modelMetadataProvider)
43+
public Email(string viewName, IModelMetadataProvider modelMetadataProvider) : this()
4244
{
4345
if (viewName == null) throw new ArgumentNullException(nameof(viewName));
4446
if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentException("View name cannot be empty.", "viewName");
4547

46-
Attachments = new List<Attachment>();
4748
ViewName = viewName;
48-
ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
49-
ViewData.Model = this;
50-
ImageEmbedder = new ImageEmbedder();
5149
}
5250

5351
/// <summary>
@@ -63,24 +61,29 @@ public Email(string viewName, string areaName, IModelMetadataProvider modelMetad
6361
/// <summary>
6462
/// The name of the view containing the email template.
6563
/// </summary>
64+
[DataMember]
6665
public string ViewName { get; set; }
6766

6867
/// <summary>
6968
/// The name of the area containing the email template.
7069
/// </summary>
71-
public string AreaName { get; set; }
70+
[DataMember]
71+
public string? AreaName { get; set; }
7272

7373
/// <summary>
7474
/// The view data to pass to the view.
7575
/// </summary>
76-
public ViewDataDictionary ViewData { get; set; }
76+
[DataMember]
77+
public Dictionary<string, object> ViewData { get; set; }
7778

7879
/// <summary>
7980
/// The attachments to send with the email.
8081
/// </summary>
82+
[DataMember]
8183
public List<Attachment> Attachments { get; set; }
8284

83-
internal ImageEmbedder ImageEmbedder { get; private set; }
85+
[DataMember]
86+
public RequestPath RequestPath { get; set; }
8487

8588
/// <summary>
8689
/// Adds an attachment to the email.
@@ -132,16 +135,13 @@ string DeriveViewNameFromClassName()
132135
return viewName;
133136
}
134137

135-
public RequestPath RequestPath { get; set; }
136-
internal HttpContextData HttpContextData { get; private set; }
137138

138139
public void CaptureHttpContext(HttpContext httpContext)
139140
{
140-
var endpoint = httpContext.GetEndpoint();
141141
var routeValues = httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues;
142-
HttpContextData = new HttpContextData { Endpoint = endpoint, RouteValues = routeValues };
143142

144143
RequestPath = new RequestPath();
144+
RequestPath.Path = httpContext.Request.Path.ToString();
145145
RequestPath.PathBase = httpContext.Request.PathBase.ToString();
146146
RequestPath.Host = httpContext.Request.Host.ToString();
147147
RequestPath.IsHttps = httpContext.Request.IsHttps;

src/Postal.AspNetCore/EmailParser.cs

+32-19
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,30 @@ public EmailParser(IEmailViewRender alternativeViewRenderer)
3232
/// <param name="emailViewOutput">The email view output.</param>
3333
/// <param name="email">The <see cref="Email"/> used to generate the output.</param>
3434
/// <returns>A <see cref="MailMessage"/> containing the email headers and content.</returns>
35-
public async Task<MailMessage> ParseAsync(string emailViewOutput, Email email)
35+
public async Task<MailMessage> ParseAsync(string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null)
3636
{
3737
var message = new MailMessage();
38-
await InitializeMailMessageAsync(message, emailViewOutput, email);
38+
await InitializeMailMessageAsync(message, emailViewOutput, email, imageEmbedder);
3939
return message;
4040
}
4141

42-
private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email)
42+
private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null)
4343
{
4444
if (string.IsNullOrWhiteSpace(emailViewOutput))
4545
{
4646
throw new ArgumentNullException(nameof(emailViewOutput));
4747
}
4848
using (var reader = new StringReader(emailViewOutput))
4949
{
50-
await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email));
50+
await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email, imageEmbedder));
5151
AssignCommonHeaders(message, email);
5252
if (message.AlternateViews.Count == 0)
5353
{
5454
var messageBody = reader.ReadToEnd().Trim();
55-
if (email.ImageEmbedder.HasImages)
55+
if (imageEmbedder != null && imageEmbedder.HasImages)
5656
{
5757
var view = AlternateView.CreateAlternateViewFromString(messageBody, new ContentType("text/html"));
58-
email.ImageEmbedder.AddImagesToView(view);
58+
imageEmbedder.AddImagesToView(view);
5959
message.AlternateViews.Add(view);
6060
message.Body = "Plain text not available.";
6161
message.IsBodyHtml = false;
@@ -112,19 +112,29 @@ private void AssignCommonHeaders(MailMessage message, Email email)
112112
private void AssignCommonHeader<T>(Email email, string header, Action<T> assign)
113113
where T : class
114114
{
115-
object value;
115+
object? value;
116116
if (email.ViewData.TryGetValue(header, out value))
117117
{
118-
var typedValue = value as T;
119-
if (typedValue != null) assign(typedValue);
118+
if (value is T typedValue)
119+
{
120+
assign(typedValue);
121+
return;
122+
}
123+
}
124+
var foundKV = email.ViewData.Where(x => String.Equals(x.Key, header, StringComparison.OrdinalIgnoreCase) && x.Value is T typedValue);
125+
if (foundKV.Any())
126+
{
127+
var val = foundKV.First();
128+
assign((T)val.Value);
129+
return;
120130
}
121131
}
122132

123-
private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email)
133+
private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email, ImageEmbedder? imageEmbedder)
124134
{
125135
if (IsAlternativeViewsHeader(key))
126136
{
127-
foreach (var view in CreateAlternativeViews(value, email))
137+
foreach (var view in CreateAlternativeViews(value, email, imageEmbedder))
128138
{
129139
message.AlternateViews.Add(await view);
130140
}
@@ -135,17 +145,17 @@ private async Task ProcessHeaderAsync(string key, string value, MailMessage mess
135145
}
136146
}
137147

138-
private IEnumerable<Task<AlternateView>> CreateAlternativeViews(string deliminatedViewNames, Email email)
148+
private IEnumerable<Task<AlternateView>> CreateAlternativeViews(string deliminatedViewNames, Email email, ImageEmbedder? imageEmbedder)
139149
{
140150
var viewNames = deliminatedViewNames.Split(new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
141-
return viewNames.Select(v => CreateAlternativeView(email, v)).ToList();
151+
return viewNames.Select(v => CreateAlternativeView(email, v, imageEmbedder)).ToList();
142152
}
143153

144-
private async Task<AlternateView> CreateAlternativeView(Email email, string alternativeViewName)
154+
private async Task<AlternateView> CreateAlternativeView(Email email, string alternativeViewName, ImageEmbedder? imageEmbedder)
145155
{
146156
var fullViewName = GetAlternativeViewName(email, alternativeViewName);
147-
var output = await alternativeViewRenderer.RenderAsync(email, fullViewName);
148-
string contentType;
157+
var output = await alternativeViewRenderer.RenderAsync(email, fullViewName, imageEmbedder);
158+
string? contentType;
149159
string body;
150160
using (var reader = new StringReader(output))
151161
{
@@ -179,7 +189,10 @@ private async Task<AlternateView> CreateAlternativeView(Email email, string alte
179189
// A different charset can be specified in the Content-Type header.
180190
// e.g. Content-Type: text/html; charset=utf-8
181191
}
182-
email.ImageEmbedder.AddImagesToView(alternativeView);
192+
if (imageEmbedder != null)
193+
{
194+
imageEmbedder.AddImagesToView(alternativeView);
195+
}
183196
return alternativeView;
184197
}
185198

@@ -206,9 +219,9 @@ private MemoryStream CreateStreamOfBody(string body)
206219
return stream;
207220
}
208221

209-
private string ParseHeadersForContentType(StringReader reader)
222+
private string? ParseHeadersForContentType(StringReader reader)
210223
{
211-
string contentType = null;
224+
string? contentType = null;
212225
ParserUtils.ParseHeaders(reader, (key, value) =>
213226
{
214227
if (key.Equals("content-type", StringComparison.OrdinalIgnoreCase))

0 commit comments

Comments
 (0)