using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http.Headers; using System.Xml; using System.Net.Http; using System.Threading.Tasks; namespace SendGridMail.Transport { public class Web : ITransport { #region Properties //TODO: Make this configurable public const String BaseUrl = "api.sendgrid.com"; public const String Endpoint = "/api/mail.send"; public const String JsonFormat = "json"; public const String XmlFormat = "xml"; private readonly NetworkCredential _credentials; #endregion /// /// Factory method for Web transport of sendgrid messages /// /// SendGrid credentials for sending mail messages /// Use https? /// New instance of the transport mechanism public static Web GetInstance(NetworkCredential credentials) { return new Web(credentials); } /// /// Creates a new Web interface for sending mail. Preference is using the Factory method. /// /// SendGrid user parameters /// Use https? internal Web(NetworkCredential credentials) { _credentials = credentials; } /// /// Delivers a message over SendGrid's Web interface /// /// public void Deliver(ISendGrid message) { var client = new HttpClient { BaseAddress = new Uri("https://" + BaseUrl) }; var content = new MultipartFormDataContent(); AttachFormParams(message, content); AttachFiles(message, content); var response = client.PostAsync(Endpoint + ".xml", content).Result; CheckForErrors(response); } /// /// Asynchronously delivers a message over SendGrid's Web interface /// /// public async Task DeliverAsync(ISendGrid message) { var client = new HttpClient { BaseAddress = new Uri("https://" + BaseUrl) }; var content = new MultipartFormDataContent(); AttachFormParams(message, content); AttachFiles(message, content); var response = await client.PostAsync(Endpoint + ".xml", content); await CheckForErrorsAsync(response); } #region Support Methods private void AttachFormParams(ISendGrid message, MultipartFormDataContent content) { var formParams = FetchFormParams(message); foreach (var keyValuePair in formParams) { content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); } } private void AttachFiles(ISendGrid message, MultipartFormDataContent content) { var files = FetchFileBodies (message); foreach (var file in files) { var fs = new FileStream(file.Key, FileMode.Open, FileAccess.Read); var fileContent = new StreamContent(fs); fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "files[" + Path.GetFileName(file.Key) + "]", FileName = Path.GetFileName(file.Key) }; fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); content.Add(fileContent); } var streamingFiles = FetchStreamingFileBodies(message); foreach (KeyValuePair file in streamingFiles) { var name = file.Key; var stream = file.Value; var fileContent = new StreamContent(stream); fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "files[" + Path.GetFileName(file.Key) + "]", FileName = Path.GetFileName(file.Key) }; fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); content.Add(fileContent); } } private void CheckForErrors (HttpResponseMessage response) { //transport error if (response.StatusCode != HttpStatusCode.OK) { throw new Exception(response.ReasonPhrase); } var content = response.Content.ReadAsStreamAsync().Result; FindErrorsInResponse(content); } private static void FindErrorsInResponse(Stream content) { using (var reader = XmlReader.Create(content)) { while (reader.Read()) { if (reader.IsStartElement()) { switch (reader.Name) { case "result": break; case "message": // success bool errors = reader.ReadToNextSibling("errors"); if (errors) throw new ProtocolViolationException(); return; case "error": // failure throw new ProtocolViolationException(); default: throw new ArgumentException("Unknown element: " + reader.Name); } } } } } private async Task CheckForErrorsAsync(HttpResponseMessage response) { //transport error if (response.StatusCode != HttpStatusCode.OK) { throw new Exception(response.ReasonPhrase); } var content = await response.Content.ReadAsStreamAsync(); FindErrorsInResponse(content); } internal List> FetchFormParams(ISendGrid message) { var result = new List> { new KeyValuePair("api_user", _credentials.UserName), new KeyValuePair("api_key", _credentials.Password), new KeyValuePair("headers", message.Headers.Count == 0 ? null : Utils.SerializeDictionary(message.Headers)), new KeyValuePair("replyto", message.ReplyTo.Length == 0 ? null : message.ReplyTo.ToList().First().Address), new KeyValuePair("from", message.From.Address), new KeyValuePair("fromname", message.From.DisplayName), new KeyValuePair("subject", message.Subject), new KeyValuePair("text", message.Text), new KeyValuePair("html", message.Html), new KeyValuePair("x-smtpapi", message.Header.AsJson()) }; if(message.To != null) { result = result.Concat(message.To.ToList().Select(a => new KeyValuePair("to[]", a.Address))) .Concat(message.To.ToList().Select(a => new KeyValuePair("toname[]", a.DisplayName))) .ToList(); } if(message.Bcc != null) { result = result.Concat(message.Bcc.ToList().Select(a => new KeyValuePair("bcc[]", a.Address))) .ToList(); } if(message.Cc != null) { result = result.Concat(message.Cc.ToList().Select(a => new KeyValuePair("cc[]", a.Address))) .ToList(); } return result.Where(r => !String.IsNullOrEmpty(r.Value)).ToList(); } internal List> FetchStreamingFileBodies(ISendGrid message) { return message.StreamedAttachments.Select(kvp => kvp).ToList(); } internal List> FetchFileBodies(ISendGrid message) { if(message.Attachments == null) return new List>(); return message.Attachments.Select(name => new KeyValuePair(name, new FileInfo(name))).ToList(); } #endregion } }