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
}
}