· 6 years ago · Feb 07, 2020, 06:48 AM
1//ReSharper disable SwitchStatementMissingSomeCases
2
3using System;
4using System.Collections.Generic;
5using System.IO;
6using System.Linq;
7using System.Threading.Tasks;
8using HtmlAgilityPack;
9using SendGrid;
10using SendGrid.Helpers.Mail;
11using Trichoma.DataAccessLayer.Database;
12using Trichoma.DataAccessLayer.Database.Logs;
13using Trichoma.Emails.Interfaces;
14using Trichoma.Emails.Models;
15using Trichoma.Enumerations;
16using Trichoma.Messaging.Interfaces;
17
18namespace Trichoma.Messaging.Services
19{
20 public class EmailService : IEmailService
21 {
22 #region Properties & Constructor
23
24 private readonly TrichomaContext _db;
25
26 public EmailService(TrichomaContext context)
27 {
28 _db = context;
29 }
30
31 #endregion
32
33 #region Public Methods
34
35 /// <summary>
36 /// Method to send an email from Trichoma no reply
37 /// </summary>
38 /// <param name="toAddresses">List of addresses email must be sent to</param>
39 /// <param name="subject">Subject of the email</param>
40 /// <param name="body">HTML content of the email</param>
41 /// <param name="ccAddresses">List of cc addresses</param>
42 /// <param name="bccAddresses">List of bcc addresses</param>
43 /// <returns></returns>
44 public Task SendEmail(List<string> toAddresses, string subject, string body, List<string> ccAddresses = null, List<string> bccAddresses = null)
45 {
46#if DEBUG
47 toAddresses = new List<string>{"pierrephilip@dupreez.org.za"};
48#endif
49
50 //Get the API key from the DB
51 var apiKey = _db.Settings.First(s => s.Key == nameof(SettingKey.SendGridApiKey)).Value;
52 //Get the from address and name
53 var fromAddress = _db.Settings.First(s => s.Key == nameof(SettingKey.EmailFromAddress)).Value;
54 var fromName = _db.Settings.First(s => s.Key == nameof(SettingKey.EmailFromName)).Value;
55
56 //Convert the HTML email to plaintext
57 var plainText = HtmlToPlainText(body);
58
59 //Create a log event for the email
60 _db.EmailLogs.Add(new EmailLog
61 {
62 Recipients = string.Join(",", toAddresses),
63 CcRecipients = (ccAddresses == null) ? null : string.Join(",", ccAddresses),
64 BccRecipients = (bccAddresses == null) ? null : string.Join(",", bccAddresses),
65 Subject = subject,
66 BodyPlainText = plainText
67 });
68
69 //Save database changes
70 _db.SaveChanges();
71
72 //Create the SendGrid client to send the email
73 var client = new SendGridClient(apiKey);
74 //Set the from details
75 var from = new EmailAddress(fromAddress, fromName);
76
77 //Create variable to store message
78 var message = new SendGridMessage();
79
80 //Check the amount of recipients to decide what type of email to send
81 if (toAddresses.Count == 1)
82 {
83 //Build up the message
84 message = MailHelper.CreateSingleEmail(from, new EmailAddress(toAddresses.First()), subject, plainText,
85 body);
86 }
87 else if (toAddresses.Count > 1)
88 {
89 //Build up list of recipients
90 var toList = new List<EmailAddress>();
91
92 //Loop through all the to addresses
93 foreach (var address in toAddresses)
94 toList.Add(new EmailAddress(address));
95
96 //Build up the message
97 message = MailHelper.CreateSingleEmailToMultipleRecipients(from, toList, subject, plainText, body);
98 }
99
100 //Add the CC addresses to the mail
101 if (ccAddresses != null)
102 foreach (var address in ccAddresses)
103 message.AddCc(new EmailAddress(address));
104
105 //Add BCC addresses
106 if (bccAddresses != null)
107 foreach (var address in bccAddresses)
108 message.AddBcc(new EmailAddress(address));
109
110 return client.SendEmailAsync(message);
111 }
112
113 #endregion
114
115 #region Private & Helper Methods
116
117 /// <summary>
118 /// Converts HTML to plain text / strips tags.
119 /// </summary>
120 /// <param name="html">The HTML.</param>
121 /// <returns></returns>
122 private string HtmlToPlainText(string html)
123 {
124 //Build up a new html document
125 var doc = new HtmlDocument();
126 //Load the HTML into the document
127 doc.LoadHtml(html);
128
129 //Create a new string writer
130 var sw = new StringWriter();
131 //Convert the html to string
132 ConvertTo(doc.DocumentNode, sw);
133 //Flush the writer
134 sw.Flush();
135 return sw.ToString();
136 }
137
138 private void ConvertContentTo(HtmlNode node, TextWriter outText)
139 {
140 //Loop through each child node in the HtmlNode
141 //Convert each subnode to text
142 foreach (var subnode in node.ChildNodes)
143 ConvertTo(subnode, outText);
144 }
145
146 private void ConvertTo(HtmlNode node, TextWriter outText)
147 {
148 switch (node.NodeType)
149 {
150 case HtmlNodeType.Comment:
151 // don't output comments
152 break;
153
154 case HtmlNodeType.Document:
155 ConvertContentTo(node, outText);
156 break;
157
158 case HtmlNodeType.Text:
159 // script and style must not be output
160 string parentName = node.ParentNode.Name;
161 if ((parentName == "script") || (parentName == "style"))
162 break;
163
164 // get text
165 var html = ((HtmlTextNode)node).Text;
166
167 // is it in fact a special closing node output as text?
168 if (HtmlNode.IsOverlappedClosingElement(html))
169 break;
170
171 // check the text is meaningful and not a bunch of whitespaces
172 if (html.Trim().Length > 0)
173 {
174 outText.Write(HtmlEntity.DeEntitize(html));
175 }
176
177 break;
178
179 case HtmlNodeType.Element:
180 switch (node.Name)
181 {
182 case "p":
183 // treat paragraphs as crlf
184 outText.Write("\r\n");
185 break;
186 case "br":
187 outText.Write("\r\n");
188 break;
189 }
190
191 if (node.HasChildNodes)
192 {
193 ConvertContentTo(node, outText);
194 }
195
196 break;
197 }
198 }
199
200 #endregion
201 }
202}