Skip to content

Commit aac4d63

Browse files
committed
tweak email template service
1 parent d389fde commit aac4d63

File tree

8 files changed

+157
-63
lines changed

8 files changed

+157
-63
lines changed

samples/EntityFramework/src/Tracker.Shared/Extensions/StringExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static partial class StringExtensions
3939
/// </summary>
4040
/// <param name="item">A String reference</param>
4141
/// <returns>
42-
/// <c>true</c> if is null or empty; otherwise, <c>false</c>.
42+
/// <see langword="true"/> if is null or empty; otherwise, <see langword="false"/>.
4343
/// </returns>
4444
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? item)
4545
{
@@ -51,7 +51,7 @@ public static bool IsNullOrEmpty([NotNullWhen(false)] this string? item)
5151
/// </summary>
5252
/// <param name="item">A String reference</param>
5353
/// <returns>
54-
/// <c>true</c> if is null or empty; otherwise, <c>false</c>.
54+
/// <see langword="true"/> if is null or empty; otherwise, <see langword="false"/>.
5555
/// </returns>
5656
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? item)
5757
{
@@ -70,7 +70,7 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? item)
7070
/// </summary>
7171
/// <param name="value">The value to check.</param>
7272
/// <returns>
73-
/// <c>true</c> if the specified <paramref name="value"/> is not <see cref="IsNullOrWhiteSpace"/>; otherwise, <c>false</c>.
73+
/// <see langword="true"/> if the specified <paramref name="value"/> is not <see cref="IsNullOrWhiteSpace"/>; otherwise, <see langword="false"/>.
7474
/// </returns>
7575
public static bool HasValue([NotNullWhen(true)] this string? value)
7676
{

src/Arbiter.Communication/Email/EmailBuilder.cs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,17 @@ public class EmailBuilder
2727
/// </summary>
2828
/// <param name="address">The sender's email address.</param>
2929
/// <param name="displayName">The optional display name for the sender.</param>
30+
/// <param name="condition">An optional predicate to determine if the sender should be set.</param>
3031
/// <returns>The current <see cref="EmailBuilder"/> instance.</returns>
3132
/// <exception cref="ArgumentException">Thrown if <paramref name="address"/> is null or empty.</exception>
32-
public EmailBuilder From(string address, string? displayName = null)
33+
public EmailBuilder From(
34+
string? address,
35+
string? displayName = null,
36+
Func<string?, string?, bool>? condition = null)
3337
{
38+
if (condition is not null && !condition(address, displayName))
39+
return this;
40+
3441
ArgumentException.ThrowIfNullOrEmpty(address);
3542

3643
_from = new EmailAddress(address, displayName);
@@ -42,10 +49,17 @@ public EmailBuilder From(string address, string? displayName = null)
4249
/// </summary>
4350
/// <param name="address">The reply-to email address.</param>
4451
/// <param name="displayName">The optional display name for the reply-to address.</param>
52+
/// <param name="condition">An optional predicate to determine if the reply-to should be added.</param>
4553
/// <returns>The current <see cref="EmailBuilder"/> instance.</returns>
4654
/// <exception cref="ArgumentException">Thrown if <paramref name="address"/> is null or empty.</exception>
47-
public EmailBuilder ReplyTo(string address, string? displayName = null)
55+
public EmailBuilder ReplyTo(
56+
string? address,
57+
string? displayName = null,
58+
Func<string?, string?, bool>? condition = null)
4859
{
60+
if (condition is not null && !condition(address, displayName))
61+
return this;
62+
4963
ArgumentException.ThrowIfNullOrEmpty(address);
5064

5165
_replyTo.Add(new EmailAddress(address, displayName));
@@ -57,10 +71,17 @@ public EmailBuilder ReplyTo(string address, string? displayName = null)
5771
/// </summary>
5872
/// <param name="address">The recipient's email address.</param>
5973
/// <param name="displayName">The optional display name for the recipient.</param>
74+
/// <param name="condition">An optional predicate to determine if the recipient should be added.</param>
6075
/// <returns>The current <see cref="EmailBuilder"/> instance.</returns>
6176
/// <exception cref="ArgumentException">Thrown if <paramref name="address"/> is null or empty.</exception>
62-
public EmailBuilder To(string address, string? displayName = null)
77+
public EmailBuilder To(
78+
string address,
79+
string? displayName = null,
80+
Func<string?, string?, bool>? condition = null)
6381
{
82+
if (condition is not null && !condition(address, displayName))
83+
return this;
84+
6485
ArgumentException.ThrowIfNullOrEmpty(address);
6586

6687
_to.Add(new EmailAddress(address, displayName));
@@ -72,10 +93,17 @@ public EmailBuilder To(string address, string? displayName = null)
7293
/// </summary>
7394
/// <param name="address">The Cc recipient's email address.</param>
7495
/// <param name="displayName">The optional display name for the Cc recipient.</param>
96+
/// <param name="condition">An optional predicate to determine if the Cc recipient should be added.</param>
7597
/// <returns>The current <see cref="EmailBuilder"/> instance.</returns>
7698
/// <exception cref="ArgumentException">Thrown if <paramref name="address"/> is null or empty.</exception>
77-
public EmailBuilder Cc(string address, string? displayName = null)
99+
public EmailBuilder Cc(
100+
string address,
101+
string? displayName = null,
102+
Func<string?, string?, bool>? condition = null)
78103
{
104+
if (condition is not null && !condition(address, displayName))
105+
return this;
106+
79107
ArgumentException.ThrowIfNullOrEmpty(address);
80108

81109
_cc.Add(new EmailAddress(address, displayName));
@@ -87,10 +115,17 @@ public EmailBuilder Cc(string address, string? displayName = null)
87115
/// </summary>
88116
/// <param name="address">The Bcc recipient's email address.</param>
89117
/// <param name="displayName">The optional display name for the Bcc recipient.</param>
118+
/// <param name="condition">An optional predicate to determine if the Bcc recipient should be added.</param>
90119
/// <returns>The current <see cref="EmailBuilder"/> instance.</returns>
91120
/// <exception cref="ArgumentException">Thrown if <paramref name="address"/> is null or empty.</exception>
92-
public EmailBuilder Bcc(string address, string? displayName = null)
121+
public EmailBuilder Bcc(
122+
string address,
123+
string? displayName = null,
124+
Func<string?, string?, bool>? condition = null)
93125
{
126+
if (condition is not null && !condition(address, displayName))
127+
return this;
128+
94129
ArgumentException.ThrowIfNullOrEmpty(address);
95130

96131
_bcc.Add(new EmailAddress(address, displayName));
@@ -177,28 +212,30 @@ public EmailBuilder AddAttachment(
177212
return this;
178213
}
179214

180-
181215
/// <summary>
182-
/// Builds and returns an instance of <see cref="EmailSenders"/> containing the configured sender and reply-to email
183-
/// addresses.
216+
/// Builds and returns an instance of <see cref="EmailSenders"/> containing the configured sender and reply-to email addresses.
184217
/// </summary>
185-
/// <remarks>Use this method to create an <see cref="EmailSenders"/> instance with the current
186-
/// configuration. Ensure that the sender and reply-to addresses have been properly set before calling this
187-
/// method.</remarks>
218+
/// <remarks>
219+
/// Use this method to create an <see cref="EmailSenders"/> instance with the current configuration.
220+
/// Ensure that the sender and reply-to addresses have been properly set before calling this method.
221+
/// </remarks>
188222
/// <returns>An <see cref="EmailSenders"/> object initialized with the sender and reply-to email addresses.</returns>
189223
public EmailSenders BuildSenders() => new(_from, _replyTo);
190224

191225
/// <summary>
192226
/// Constructs an <see cref="EmailRecipients"/> object using the current recipient lists.
193227
/// </summary>
194-
/// <returns>An <see cref="EmailRecipients"/> instance containing the current "To", "CC", and "BCC" recipient lists.</returns>
228+
/// <returns>
229+
/// An <see cref="EmailRecipients"/> instance containing the current "To", "Cc", and "Bcc" recipient lists.
230+
/// </returns>
195231
public EmailRecipients BuildRecipients() => new(_to, _cc, _bcc);
196232

197233
/// <summary>
198-
/// Builds and returns an <see cref="EmailContent"/> object containing the email's subject, HTML body, and text
199-
/// body.
234+
/// Builds and returns an <see cref="EmailContent"/> object containing the email's subject, HTML body, and text body.
200235
/// </summary>
201-
/// <returns>An <see cref="EmailContent"/> instance initialized with the current subject, HTML body, and text body values.</returns>
236+
/// <returns>
237+
/// An <see cref="EmailContent"/> instance initialized with the current subject, HTML body, and text body values.
238+
/// </returns>
202239
public EmailContent BuildContent() => new(_subject, _htmlBody, _textBody);
203240

204241
/// <summary>
@@ -226,7 +263,6 @@ public EmailMessage BuildMessage()
226263
return new EmailMessage(senders, recipients, content, _headers, _attachments);
227264
}
228265

229-
230266
/// <summary>
231267
/// Creates a new <see cref="EmailBuilder"/> instance, optionally setting the sender address and display name.
232268
/// </summary>

src/Arbiter.Communication/Email/EmailConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class EmailConfiguration
4444
/// Gets or sets a value indicating whether to use SSL when connecting to the SMTP server.
4545
/// </summary>
4646
/// <remarks>
47-
/// The default value is <c>true</c>.
47+
/// The default value is <see langword="true"/>.
4848
/// </remarks>
4949
public bool UseSSL { get; set; } = true;
5050

src/Arbiter.Communication/Email/EmailTemplateService.cs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,64 @@ namespace Arbiter.Communication.Email;
1616
/// </remarks>
1717
public class EmailTemplateService : IEmailTemplateService
1818
{
19-
private readonly ILogger<EmailTemplateService> _logger;
20-
private readonly IOptions<EmailConfiguration> _options;
21-
private readonly ITemplateService _templateService;
22-
private readonly IEmailDeliveryService _deliveryService;
23-
2419
/// <summary>
2520
/// Initializes a new instance of the <see cref="EmailTemplateService"/> class.
2621
/// </summary>
2722
/// <param name="logger">The logger for diagnostic and error messages.</param>
2823
/// <param name="options">The email options containing sender and template configuration.</param>
2924
/// <param name="templateService">The template service for applying models to templates.</param>
3025
/// <param name="deliveryService">The service responsible for delivering emails.</param>
26+
/// <exception cref="ArgumentNullException">
27+
/// Thrown if <paramref name="logger"/>, <paramref name="options"/>, <paramref name="templateService"/>, or <paramref name="deliveryService"/> is <see langword="null"/>.
28+
/// </exception>
3129
public EmailTemplateService(
3230
ILogger<EmailTemplateService> logger,
3331
IOptions<EmailConfiguration> options,
3432
ITemplateService templateService,
3533
IEmailDeliveryService deliveryService)
3634
{
37-
_logger = logger;
38-
_options = options;
39-
_templateService = templateService;
40-
_deliveryService = deliveryService;
35+
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
36+
Options = options ?? throw new ArgumentNullException(nameof(options));
37+
TemplateService = templateService ?? throw new ArgumentNullException(nameof(templateService));
38+
DeliveryService = deliveryService ?? throw new ArgumentNullException(nameof(deliveryService));
4139
}
4240

41+
/// <summary>
42+
/// Gets the logger used for diagnostic and error messages.
43+
/// </summary>
44+
protected ILogger Logger { get; }
45+
46+
/// <summary>
47+
/// Gets the email configuration options.
48+
/// </summary>
49+
protected IOptions<EmailConfiguration> Options { get; }
50+
51+
/// <summary>
52+
/// Gets the template service for applying models to templates.
53+
/// </summary>
54+
protected ITemplateService TemplateService { get; }
55+
56+
/// <summary>
57+
/// Gets the service responsible for delivering emails.
58+
/// </summary>
59+
protected IEmailDeliveryService DeliveryService { get; }
60+
4361
/// <summary>
4462
/// Sends an email using a named template, binding the specified model to the template and delivering it to the given recipients.
4563
/// </summary>
4664
/// <typeparam name="TModel">The type of the model used for template binding.</typeparam>
4765
/// <param name="templateName">The name of the template to use for the email.</param>
4866
/// <param name="emailModel">The model containing data to bind to the template.</param>
4967
/// <param name="recipients">The recipients of the email, including To, Cc, and Bcc addresses.</param>
50-
/// <param name="senders">The sender and optional reply-to addresses for the email. If null, default senders from configuration are used.</param>
68+
/// <param name="senders">The sender and optional reply-to addresses for the email. If <see langword="null"/>, default senders from configuration are used.</param>
5169
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
5270
/// <returns>
5371
/// A <see cref="Task{TResult}"/> that resolves to an <see cref="EmailResult"/> indicating the outcome of the send operation,
5472
/// including success status, message, and any exception details.
5573
/// </returns>
56-
/// <exception cref="ArgumentException">Thrown if <paramref name="templateName"/> is null or empty.</exception>
57-
/// <exception cref="ArgumentNullException">Thrown if <paramref name="emailModel"/> is null.</exception>
74+
/// <exception cref="ArgumentException">Thrown if <paramref name="templateName"/> is <see langword="null"/> or empty.</exception>
75+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="emailModel"/> is <see langword="null"/>.</exception>
76+
/// <exception cref="Exception">Thrown if an error occurs during template loading or email sending.</exception>
5877
public async Task<EmailResult> Send<TModel>(
5978
string templateName,
6079
TModel emailModel,
@@ -65,7 +84,7 @@ public async Task<EmailResult> Send<TModel>(
6584
ArgumentException.ThrowIfNullOrEmpty(templateName);
6685
ArgumentNullException.ThrowIfNull(emailModel);
6786

68-
var options = _options.Value;
87+
var options = Options.Value;
6988

7089
try
7190
{
@@ -74,18 +93,17 @@ public async Task<EmailResult> Send<TModel>(
7493
? string.Format(options.TemplateResourceFormat, templateName)
7594
: templateName;
7695

77-
var emailTemplate = _templateService.GetResourceTemplate<EmailTemplate?>(templateAssembly, resourceName);
78-
if (emailTemplate == null)
96+
if (!TemplateService.TryGetResourceTemplate<EmailTemplate>(templateAssembly, resourceName, out var emailTemplate))
7997
{
80-
_logger.LogError("Could not find email template: {TemplateName}", templateName);
98+
Logger.LogError("Could not find email template: {TemplateName}", templateName);
8199
return EmailResult.Fail($"Could not find template '{templateName}'");
82100
}
83101

84-
return await Send(emailTemplate.Value, emailModel, recipients, senders, cancellationToken).ConfigureAwait(false);
102+
return await Send(emailTemplate, emailModel, recipients, senders, cancellationToken).ConfigureAwait(false);
85103
}
86104
catch (Exception ex)
87105
{
88-
_logger.LogError(ex, "Error sending email: {ErrorMessage}", ex.Message);
106+
Logger.LogError(ex, "Error sending email: {ErrorMessage}", ex.Message);
89107
throw;
90108
}
91109
}
@@ -97,13 +115,14 @@ public async Task<EmailResult> Send<TModel>(
97115
/// <param name="emailTemplate">The email template to use for the message.</param>
98116
/// <param name="emailModel">The model containing data to bind to the template.</param>
99117
/// <param name="recipients">The recipients of the email, including To, Cc, and Bcc addresses.</param>
100-
/// <param name="senders">The sender and optional reply-to addresses for the email. If null, default senders from configuration are used.</param>
118+
/// <param name="senders">The sender and optional reply-to addresses for the email. If <see langword="null"/>, default senders from configuration are used.</param>
101119
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
102120
/// <returns>
103121
/// A <see cref="Task{TResult}"/> that resolves to an <see cref="EmailResult"/> indicating the outcome of the send operation,
104122
/// including success status, message, and any exception details.
105123
/// </returns>
106-
/// <exception cref="ArgumentNullException">Thrown if <paramref name="emailModel"/> is null.</exception>
124+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="emailModel"/> is <see langword="null"/>.</exception>
125+
/// <exception cref="Exception">Thrown if an error occurs during template application or email sending.</exception>
107126
public async Task<EmailResult> Send<TModel>(
108127
EmailTemplate emailTemplate,
109128
TModel emailModel,
@@ -113,13 +132,13 @@ public async Task<EmailResult> Send<TModel>(
113132
{
114133
ArgumentNullException.ThrowIfNull(emailModel);
115134

116-
var options = _options.Value;
135+
var options = Options.Value;
117136

118137
try
119138
{
120-
var subject = _templateService.ApplyTemplate(emailTemplate.Subject, emailModel);
121-
var htmlBody = _templateService.ApplyTemplate(emailTemplate.HtmlBody, emailModel);
122-
var textBody = _templateService.ApplyTemplate(emailTemplate.TextBody, emailModel);
139+
var subject = TemplateService.ApplyTemplate(emailTemplate.Subject, emailModel);
140+
var htmlBody = TemplateService.ApplyTemplate(emailTemplate.HtmlBody, emailModel);
141+
var textBody = TemplateService.ApplyTemplate(emailTemplate.TextBody, emailModel);
123142

124143
var fromName = options.FromName;
125144
var fromEmail = options.FromAddress;
@@ -128,18 +147,18 @@ public async Task<EmailResult> Send<TModel>(
128147

129148
if (localSenders.From.Address.IsNullOrWhiteSpace())
130149
{
131-
_logger.LogError("From address is not configured in EmailOptions.");
150+
Logger.LogError("From address is not configured in EmailOptions.");
132151
return EmailResult.Fail("From address is not configured.");
133152
}
134153

135154
var content = new EmailContent(subject, htmlBody, textBody);
136155
var message = new EmailMessage(localSenders, recipients, content);
137156

138-
return await _deliveryService.Send(message, cancellationToken).ConfigureAwait(false);
157+
return await DeliveryService.Send(message, cancellationToken).ConfigureAwait(false);
139158
}
140159
catch (Exception ex)
141160
{
142-
_logger.LogError(ex, "Error Sending Email: {ErrorMessage}", ex.Message);
161+
Logger.LogError(ex, "Error Sending Email: {ErrorMessage}", ex.Message);
143162
throw;
144163
}
145164
}

src/Arbiter.Communication/Sms/SmsTemplateService.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,13 @@ public async Task<SmsResult> Send<TModel>(
7373
? string.Format(options.TemplateResourceFormat, templateName)
7474
: templateName;
7575

76-
var template = _templateService.GetResourceTemplate<SmsTemplate?>(templateAssembly, resourceName);
77-
if (template == null)
76+
if (!_templateService.TryGetResourceTemplate<SmsTemplate>(templateAssembly, resourceName, out var template))
7877
{
7978
_logger.LogError("Could not find template: {TemplateName}", templateName);
8079
return SmsResult.Fail($"Could not find template '{templateName}'");
8180
}
8281

83-
return await Send(template.Value, model, recipient, sender, cancellationToken)
82+
return await Send(template, model, recipient, sender, cancellationToken)
8483
.ConfigureAwait(false);
8584
}
8685
catch (Exception ex)

0 commit comments

Comments
 (0)