Skip to content

Commit 2e01be4

Browse files
Fix a bug to read the attachments in the memory to send to the provider.
1 parent e57dc36 commit 2e01be4

2 files changed

Lines changed: 96 additions & 36 deletions

File tree

src/Emailing/EmailManager.cs

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,70 @@ public async Task SendAsync<TModel>(Email<TModel> email, CancellationToken cance
4949

5050
var senderEmailAddress = this.options.Value.SenderEmailAddress!;
5151

52-
foreach (var recipient in email.Recipients)
52+
// Copy the attachments in memory.
53+
var attachmentsContent = new List<MemoryStream>(email.Attachments.Count);
54+
55+
foreach (var attachment in email.Attachments)
5356
{
54-
// Render the subject
55-
using var subjectOutputWriter = new StringWriter();
57+
var attachementStream = new MemoryStream();
58+
59+
await attachment.Content.CopyToAsync(attachementStream, cancellationToken);
60+
61+
attachementStream.Position = 0;
62+
63+
attachmentsContent.Add(attachementStream);
64+
}
5665

57-
var textTemplateRenderContext = new TextTemplateRenderContext(this.serviceProvider);
66+
try
67+
{
68+
foreach (var recipient in email.Recipients)
69+
{
70+
// Render the subject
71+
using var subjectOutputWriter = new StringWriter();
5872

59-
await email.Template.Subject.RenderAsync(recipient.Model, subjectOutputWriter, textTemplateRenderContext, cancellationToken);
73+
var textTemplateRenderContext = new TextTemplateRenderContext(this.serviceProvider);
6074

61-
var subject = subjectOutputWriter.ToString();
75+
await email.Template.Subject.RenderAsync(recipient.Model, subjectOutputWriter, textTemplateRenderContext, cancellationToken);
6276

63-
// Render the HTML content
64-
using var htmlContentOutputWriter = new StringWriter();
77+
var subject = subjectOutputWriter.ToString();
6578

66-
textTemplateRenderContext = new TextTemplateRenderContext(this.serviceProvider);
79+
// Render the HTML content
80+
using var htmlContentOutputWriter = new StringWriter();
6781

68-
await email.Template.HtmlBody.RenderAsync(recipient.Model, htmlContentOutputWriter, textTemplateRenderContext, cancellationToken);
82+
textTemplateRenderContext = new TextTemplateRenderContext(this.serviceProvider);
6983

70-
var htmlContent = htmlContentOutputWriter.ToString();
84+
await email.Template.HtmlBody.RenderAsync(recipient.Model, htmlContentOutputWriter, textTemplateRenderContext, cancellationToken);
7185

72-
var message = new EmailMessage(
73-
new EmailContact(senderEmailAddress, string.Empty),
74-
new EmailContact(recipient.Address, recipient.DisplayName),
75-
subject,
76-
htmlContent)
77-
{
78-
Importance = email.Importance,
79-
};
86+
var htmlContent = htmlContentOutputWriter.ToString();
8087

81-
foreach (var attachment in email.Attachments)
88+
var message = new EmailMessage(
89+
new EmailContact(senderEmailAddress, string.Empty),
90+
new EmailContact(recipient.Address, recipient.DisplayName),
91+
subject,
92+
htmlContent)
93+
{
94+
Importance = email.Importance,
95+
};
96+
97+
for (int i = 0; i < email.Attachments.Count; i++)
98+
{
99+
attachmentsContent[i].Position = 0;
100+
101+
var emailAttachement = email.Attachments[i];
102+
var newEmailAttachement = new EmailAttachment(emailAttachement.FileName, emailAttachement.ContentType, attachmentsContent[i]);
103+
104+
message.Attachments.Add(newEmailAttachement);
105+
}
106+
107+
await this.provider.SendAsync(message, cancellationToken);
108+
}
109+
}
110+
finally
111+
{
112+
foreach (var attachmentContent in attachmentsContent)
82113
{
83-
message.Attachments.Add(attachment);
114+
await attachmentContent.DisposeAsync();
84115
}
85-
86-
await this.provider.SendAsync(message, cancellationToken);
87116
}
88117
}
89118

tests/Emailing.Tests/EmailManagerTest.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace PosInformatique.Foundations.Emailing.Tests
88
{
9+
using System.Reflection;
910
using Microsoft.Extensions.Options;
1011
using PosInformatique.Foundations.EmailAddresses;
1112
using PosInformatique.Foundations.MediaTypes;
@@ -130,16 +131,11 @@ public async Task SendAsync()
130131
var emailAddressRecipient1 = EmailAddress.Parse("email1@domain.com");
131132
var emailAddressRecipient2 = EmailAddress.Parse("email2@domain.com");
132133

133-
var content1 = new Mock<Stream>(MockBehavior.Strict);
134-
content1.Setup(c => c.CanRead)
135-
.Returns(true);
134+
var content1 = new MemoryStream([1, 2]);
135+
var content2 = new MemoryStream([3, 4]);
136136

137-
var content2 = new Mock<Stream>(MockBehavior.Strict);
138-
content2.Setup(c => c.CanRead)
139-
.Returns(true);
140-
141-
var attachment1 = new EmailAttachment("Attachment1", MimeTypes.Application.Pdf, content1.Object);
142-
var attachment2 = new EmailAttachment("Attachment2", MimeTypes.Application.Docx, content2.Object);
137+
var attachment1 = new EmailAttachment("Attachment1", MimeTypes.Application.Pdf, content1);
138+
var attachment2 = new EmailAttachment("Attachment2", MimeTypes.Application.Docx, content2);
143139

144140
var email = new Email<Model>(template)
145141
{
@@ -161,38 +157,66 @@ public async Task SendAsync()
161157
var options = new EmailingOptions();
162158
options.SenderEmailAddress = sender;
163159

160+
var contentStreams = new List<Stream>();
161+
164162
var provider = new Mock<IEmailProvider>(MockBehavior.Strict);
165163
provider.Setup(p => p.SendAsync(It.Is<EmailMessage>(m => m.To.Email == emailAddressRecipient1), cancellationToken))
166164
.Callback((EmailMessage m, CancellationToken _) =>
167165
{
168-
m.Attachments.Should().Equal(attachment1, attachment2);
166+
m.Attachments[0].Content.As<MemoryStream>().Position.Should().Be(0);
167+
m.Attachments[0].Content.As<MemoryStream>().ToArray().Should().Equal(1, 2);
168+
m.Attachments[0].ContentType.Should().Be(MimeTypes.Application.Pdf);
169+
m.Attachments[0].FileName.Should().Be("Attachment1");
170+
m.Attachments[1].Content.As<MemoryStream>().Position.Should().Be(0);
171+
m.Attachments[1].Content.As<MemoryStream>().ToArray().Should().Equal(3, 4);
172+
m.Attachments[1].ContentType.Should().Be(MimeTypes.Application.Docx);
173+
m.Attachments[1].FileName.Should().Be("Attachment2");
169174
m.From.Email.Should().BeSameAs(sender);
170175
m.From.DisplayName.Should().BeEmpty();
171176
m.Importance.Should().Be(EmailImportance.High);
172177
m.Subject.Should().Be("Subject 1");
173178
m.HtmlContent.Should().Be("HTML Content 1");
174179
m.To.DisplayName.Should().Be("The display name 1");
180+
181+
contentStreams.Add(m.Attachments[0].Content);
182+
contentStreams.Add(m.Attachments[1].Content);
175183
})
176184
.Returns(Task.CompletedTask);
177185
provider.Setup(p => p.SendAsync(It.Is<EmailMessage>(m => m.To.Email == emailAddressRecipient2), cancellationToken))
178186
.Callback((EmailMessage m, CancellationToken _) =>
179187
{
180-
m.Attachments.Should().Equal(attachment1, attachment2);
188+
m.Attachments[0].Content.As<MemoryStream>().Position.Should().Be(0);
189+
m.Attachments[0].Content.As<MemoryStream>().ToArray().Should().Equal(1, 2);
190+
m.Attachments[0].ContentType.Should().Be(MimeTypes.Application.Pdf);
191+
m.Attachments[0].FileName.Should().Be("Attachment1");
192+
m.Attachments[1].Content.As<MemoryStream>().Position.Should().Be(0);
193+
m.Attachments[1].Content.As<MemoryStream>().ToArray().Should().Equal(3, 4);
194+
m.Attachments[1].ContentType.Should().Be(MimeTypes.Application.Docx);
195+
m.Attachments[1].FileName.Should().Be("Attachment2");
181196
m.From.Email.Should().BeSameAs(sender);
182197
m.From.DisplayName.Should().BeEmpty();
183198
m.Importance.Should().Be(EmailImportance.High);
184199
m.Subject.Should().Be("Subject 2");
185200
m.HtmlContent.Should().Be("HTML Content 2");
186201
m.To.DisplayName.Should().Be("The display name 2");
202+
203+
contentStreams.Add(m.Attachments[0].Content);
204+
contentStreams.Add(m.Attachments[1].Content);
187205
})
188206
.Returns(Task.CompletedTask);
189207

190208
var manager = new EmailManager(Options.Create(options), provider.Object, serviceProvider);
191209

192210
await manager.SendAsync(email, cancellationToken);
193211

194-
content1.VerifyAll();
195-
content2.VerifyAll();
212+
foreach (var contentStream in contentStreams)
213+
{
214+
IsOpen(contentStream).Should().BeFalse();
215+
}
216+
217+
IsOpen(email.Attachments[0].Content).Should().BeTrue();
218+
IsOpen(email.Attachments[1].Content).Should().BeTrue();
219+
196220
htmlBody.VerifyAll();
197221
provider.VerifyAll();
198222
subject.VerifyAll();
@@ -211,6 +235,13 @@ await manager.Invoking(m => m.SendAsync<Model>(null, default))
211235
.WithParameterName("email");
212236
}
213237

238+
private static bool IsOpen(Stream stream)
239+
{
240+
var fieldIsOpen = typeof(MemoryStream).GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
241+
242+
return (bool)fieldIsOpen.GetValue(stream);
243+
}
244+
214245
internal sealed class Model
215246
{
216247
}

0 commit comments

Comments
 (0)