From 05eb3c902b1b2328841bef048f6d1431307c69b1 Mon Sep 17 00:00:00 2001 From: Jericho Date: Sat, 8 May 2021 10:18:01 -0400 Subject: [PATCH] (GH-391) Add MailSettingsConverter and use it instead of manually serializing the mail settings --- .../Resources/MailTests.cs | 17 ++- Source/StrongGrid/Models/MailSettings.cs | 4 + Source/StrongGrid/Resources/Mail.cs | 73 +++++------ .../Utilities/MailSettingsConverter.cs | 120 ++++++++++++++++++ 4 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 Source/StrongGrid/Utilities/MailSettingsConverter.cs diff --git a/Source/StrongGrid.UnitTests/Resources/MailTests.cs b/Source/StrongGrid.UnitTests/Resources/MailTests.cs index aa311a6f..7e1a7625 100644 --- a/Source/StrongGrid.UnitTests/Resources/MailTests.cs +++ b/Source/StrongGrid.UnitTests/Resources/MailTests.cs @@ -41,8 +41,23 @@ public async Task SendAsync() } }; + var mailSettings = new MailSettings() + { + BypassBounceManagement = true, + BypassListManagement = true, + BypassSpamManagement = true, + BypassUnsubscribeManagement = true, + SandboxModeEnabled = true, + Footer = new FooterSettings() + { + Enabled = true, + HtmlContent = "abc123", + TextContent = "yzx890" + } + }; + // Act - var result = await mail.SendAsync(personalizations, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, MailPriority.Normal, CancellationToken.None).ConfigureAwait(false); + var result = await mail.SendAsync(personalizations, null, null, null, null, null, null, null, null, null, null, null, null, null, mailSettings, null, MailPriority.Normal, CancellationToken.None).ConfigureAwait(false); // Assert mockHttp.VerifyNoOutstandingExpectation(); diff --git a/Source/StrongGrid/Models/MailSettings.cs b/Source/StrongGrid/Models/MailSettings.cs index bb08b5b4..30ff48fc 100644 --- a/Source/StrongGrid/Models/MailSettings.cs +++ b/Source/StrongGrid/Models/MailSettings.cs @@ -1,8 +1,12 @@ +using Newtonsoft.Json; +using StrongGrid.Utilities; + namespace StrongGrid.Models { /// /// Mail settings. /// + [JsonConverter(typeof(MailSettingsConverter))] public class MailSettings { /// diff --git a/Source/StrongGrid/Resources/Mail.cs b/Source/StrongGrid/Resources/Mail.cs index e43ef825..94564526 100644 --- a/Source/StrongGrid/Resources/Mail.cs +++ b/Source/StrongGrid/Resources/Mail.cs @@ -188,19 +188,18 @@ public async Task SendAsync( numberOfRecipients += personalizationsCopy.Sum(p => p.Bcc?.Count(r => r != null) ?? 0); if (numberOfRecipients >= 1000) throw new ArgumentOutOfRangeException(nameof(numberOfRecipients), numberOfRecipients, "The total number of recipients must be less than 1000"); - var isDynamicTemplate = Template.IsDynamic(templateId); - var personalizationConverter = new MailPersonalizationConverter(isDynamicTemplate); + // Get the priority headers + if (!_priorityHeaders.TryGetValue(priority, out KeyValuePair[] priorityHeaders)) + { + throw new Exception($"{priority} is an unknown priority"); + } - // Manually serialize the MailSettings object because the shape of the C# class does not match the shape of the desired JSON. - // The C# class was intentionaly simplified to improve developers experience. - var mailSettingsJsonObject = new JObject(); - mailSettingsJsonObject.AddPropertyIfValue("bypass_list_management/enable", mailSettings.BypassListManagement); - mailSettingsJsonObject.AddPropertyIfValue("bypass_spam_management/enable", mailSettings.BypassSpamManagement); - mailSettingsJsonObject.AddPropertyIfValue("bypass_bounce_management/enable", mailSettings.BypassBounceManagement); - mailSettingsJsonObject.AddPropertyIfValue("bypass_unsubscribe_management/enable", mailSettings.BypassUnsubscribeManagement); - mailSettingsJsonObject.AddPropertyIfValue("footer", mailSettings.Footer); - mailSettingsJsonObject.AddPropertyIfValue("sandbox_mode/enable", mailSettings.SandboxModeEnabled); + // Combine headers with priority headers making sure not to duplicate priority headers + var combinedHeaders = (headers ?? Array.Empty>()) + .Where(kvp => !priorityHeaders.Any(p => p.Key.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase))) + .Concat(priorityHeaders); + // Serialize the mail message var data = new JObject(); data.AddPropertyIfValue("from", from); data.AddPropertyIfValue("reply_to", replyTo); @@ -213,45 +212,17 @@ public async Task SendAsync( data.AddPropertyIfValue("batch_id", batchId); data.AddPropertyIfValue("asm", unsubscribeOptions); data.AddPropertyIfValue("ip_pool_name", ipPoolName); - data.AddPropertyIfValue("mail_settings", mailSettingsJsonObject); + data.AddPropertyIfValue("mail_settings", mailSettings); data.AddPropertyIfValue("tracking_settings", trackingSettings); - data.AddPropertyIfValue("personalizations", personalizationsCopy, personalizationConverter); - - if (_priorityHeaders.TryGetValue(priority, out KeyValuePair[] priorityHeaders)) - { - headers = (headers ?? Array.Empty>()).Concat(priorityHeaders); - } - else - { - throw new Exception($"{priority} is an unknown priority"); - } - - if (headers != null && headers.Any()) - { - var hdrs = new JObject(); - foreach (var header in headers) - { - hdrs.Add(header.Key, header.Value); - } - - data.Add("headers", hdrs); - } - - if (customArgs != null && customArgs.Any()) - { - var args = new JObject(); - foreach (var customArg in customArgs) - { - args.Add(customArg.Key, customArg.Value); - } - - data.Add("custom_args", args); - } + data.AddPropertyIfValue("personalizations", personalizationsCopy, new MailPersonalizationConverter(Template.IsDynamic(templateId))); + data.AddPropertyIfValue("headers", ConvertEnumerationToJObject(combinedHeaders)); + data.AddPropertyIfValue("custom_args", ConvertEnumerationToJObject(customArgs)); // SendGrid does not allow emails that exceed 30MB var contentSize = JsonConvert.SerializeObject(data, Formatting.None).Length; if (contentSize > MAX_EMAIL_SIZE) throw new Exception("Email exceeds the size limit"); + // Send the request var response = await _client .PostAsync($"{_endpoint}/send") .WithJsonBody(data) @@ -259,6 +230,7 @@ public async Task SendAsync( .AsResponse() .ConfigureAwait(false); + // Get the messageId from the response if (response.Message.Headers.TryGetValues("X-Message-Id", out IEnumerable values)) { return values?.FirstOrDefault(); @@ -288,5 +260,18 @@ private static MailAddress[] EnsureRecipientsNamesAreQuoted(MailAddress[] recipi .Union(recipientsWithoutName) .ToArray(); } + + private static JObject ConvertEnumerationToJObject(IEnumerable> items) + { + if (items == null || !items.Any()) return null; + + var obj = new JObject(); + foreach (var item in items) + { + obj.Add(item.Key, item.Value); + } + + return obj; + } } } diff --git a/Source/StrongGrid/Utilities/MailSettingsConverter.cs b/Source/StrongGrid/Utilities/MailSettingsConverter.cs new file mode 100644 index 00000000..441e8b67 --- /dev/null +++ b/Source/StrongGrid/Utilities/MailSettingsConverter.cs @@ -0,0 +1,120 @@ +using Newtonsoft.Json; +using StrongGrid.Models; +using System; + +namespace StrongGrid.Utilities +{ + /// + /// Converts a MailSettings object to and from JSON. + /// + /// + internal class MailSettingsConverter : JsonConverter + { + public MailSettingsConverter() + { + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(MailSettings); + } + + /// + /// Gets a value indicating whether this can read JSON. + /// + /// + /// true if this can read JSON; otherwise, false. + /// + public override bool CanRead + { + get { return false; } + } + + /// + /// Gets a value indicating whether this can write JSON. + /// + /// + /// true if this can write JSON; otherwise, false. + /// + public override bool CanWrite + { + get { return true; } + } + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) return; + + writer.WriteStartObject(); + + var mailSettings = (MailSettings)value; + + writer.WritePropertyName("bypass_list_management"); + writer.WriteStartObject(); + writer.WritePropertyName("enable"); + serializer.Serialize(writer, mailSettings.BypassListManagement); + writer.WriteEndObject(); + + writer.WritePropertyName("bypass_spam_management"); + writer.WriteStartObject(); + writer.WritePropertyName("enable"); + serializer.Serialize(writer, mailSettings.BypassSpamManagement); + writer.WriteEndObject(); + + writer.WritePropertyName("bypass_bounce_management"); + writer.WriteStartObject(); + writer.WritePropertyName("enable"); + serializer.Serialize(writer, mailSettings.BypassBounceManagement); + writer.WriteEndObject(); + + writer.WritePropertyName("bypass_unsubscribe_management"); + writer.WriteStartObject(); + writer.WritePropertyName("enable"); + serializer.Serialize(writer, mailSettings.BypassUnsubscribeManagement); + writer.WriteEndObject(); + + if (mailSettings.Footer != null) + { + writer.WritePropertyName("footer"); + serializer.Serialize(writer, mailSettings.Footer); + } + + writer.WritePropertyName("sandbox_mode"); + writer.WriteStartObject(); + writer.WritePropertyName("enable"); + serializer.Serialize(writer, mailSettings.SandboxModeEnabled); + writer.WriteEndObject(); + + // End of JSON serialization + writer.WriteEndObject(); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// + /// The object value. + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotSupportedException("The MailSettingsConverter can only be used to write JSON"); + } + } +}