From ed82d9fab791e62bc8833dc0995d4df14db6ff49 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 1 Feb 2016 12:46:13 -0600 Subject: [PATCH 01/52] Add an overloaded RequestAsync to accept JArray and also refactor it to be able to handle content with any HTTP verb (as opposed to only POST and PATCH). These two changes are necessary in order to handle calls such as 'Delete Multiple lists' for example which allows an array of integers to be in the body of the DELETE request. --- SendGrid/SendGrid/Client.cs | 80 ++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 6e86be196..76a1ecef8 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -1,12 +1,12 @@ -using System; +using Newtonsoft.Json.Linq; +using SendGrid.Resources; +using System; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; -using System.Threading.Tasks; using System.Text; -using SendGrid.Resources; -using System.Net; -using Newtonsoft.Json.Linq; +using System.Threading.Tasks; namespace SendGrid { @@ -51,6 +51,32 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/") /// An JObject representing the resource's data /// An asyncronous task private async Task RequestAsync(Methods method, string endpoint, JObject data) + { + var content = (data == null ? null : new StringContent(data.ToString(), Encoding.UTF8, MediaType)); + return await RequestAsync(method, endpoint, content); + } + + /// + /// Create a client that connects to the SendGrid Web API + /// + /// HTTP verb, case-insensitive + /// Resource endpoint, do not prepend slash + /// An JArray representing the resource's data + /// An asyncronous task + private async Task RequestAsync(Methods method, string endpoint, JArray data) + { + var content = (data == null ? null : new StringContent(data.ToString(), Encoding.UTF8, MediaType)); + return await RequestAsync(method, endpoint, content); + } + + /// + /// Create a client that connects to the SendGrid Web API + /// + /// HTTP verb, case-insensitive + /// Resource endpoint, do not prepend slash + /// A StringContent representing the content of the http request + /// An asyncronous task + private async Task RequestAsync(Methods method, string endpoint, StringContent content) { using (var client = new HttpClient()) { @@ -62,31 +88,30 @@ private async Task RequestAsync(Methods method, string endp client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "sendgrid/" + Version + ";csharp"); + var methodAsString = ""; switch (method) { - case Methods.GET: - return await client.GetAsync(endpoint); - case Methods.POST: - return await client.PostAsJsonAsync(endpoint, data); - case Methods.PATCH: - endpoint = _baseUri + endpoint; - StringContent content = new StringContent(data.ToString(), Encoding.UTF8, MediaType); - HttpRequestMessage request = new HttpRequestMessage - { - Method = new HttpMethod("PATCH"), - RequestUri = new Uri(endpoint), - Content = content - }; - return await client.SendAsync(request); - case Methods.DELETE: - return await client.DeleteAsync(endpoint); + case Methods.GET: methodAsString = "GET"; break; + case Methods.POST: methodAsString = "POST"; break; + case Methods.PATCH: methodAsString = "PATCH"; break; + case Methods.DELETE: methodAsString = "DELETE"; break; default: - HttpResponseMessage response = new HttpResponseMessage(); - response.StatusCode = HttpStatusCode.MethodNotAllowed; var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; - response.Content = new StringContent(message); + var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) + { + Content = new StringContent(message) + }; return response; } + + var postRequest = new HttpRequestMessage + { + Method = new HttpMethod(methodAsString), + RequestUri = new Uri(_baseUri + endpoint), + Content = content + }; + return await client.SendAsync(postRequest); + } catch (Exception ex) { @@ -104,7 +129,7 @@ private async Task RequestAsync(Methods method, string endp /// The resulting message from the API call public async Task Get(string endpoint) { - return await RequestAsync(Methods.GET, endpoint, null); + return await RequestAsync(Methods.GET, endpoint, (StringContent)null); } /// Resource endpoint, do not prepend slash @@ -116,10 +141,11 @@ public async Task Post(string endpoint, JObject data) } /// Resource endpoint, do not prepend slash + /// An optional JArray representing the resource's data /// The resulting message from the API call - public async Task Delete(string endpoint) + public async Task Delete(string endpoint, JArray data = null) { - return await RequestAsync(Methods.DELETE, endpoint, null); + return await RequestAsync(Methods.DELETE, endpoint, data); } /// Resource endpoint, do not prepend slash From eb5a119d8778c80d00f6a5c41392034564107835 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 1 Feb 2016 13:18:01 -0600 Subject: [PATCH 02/52] Reformat --- SendGrid/SendGrid/Client.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 76a1ecef8..709ea254f 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -104,7 +104,7 @@ private async Task RequestAsync(Methods method, string endp return response; } - var postRequest = new HttpRequestMessage + var postRequest = new HttpRequestMessage() { Method = new HttpMethod(methodAsString), RequestUri = new Uri(_baseUri + endpoint), @@ -115,11 +115,11 @@ private async Task RequestAsync(Methods method, string endp } catch (Exception ex) { - HttpResponseMessage response = new HttpResponseMessage(); - string message; - message = (ex is HttpRequestException) ? ".NET HttpRequestException" : ".NET Exception"; - message = message + ", raw message: \n\n"; - response.Content = new StringContent(message + ex.Message); + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent(message) + }; return response; } } From 0b66b5c659d83d4aa77eaa836d3f1e9dca74c78c Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 2 Feb 2016 16:52:05 -0600 Subject: [PATCH 03/52] Strongly typed GlobalStats --- SendGrid/Example/Example.csproj | 3 ++ SendGrid/Example/Program.cs | 57 +++++++++------------- SendGrid/SendGrid/Model/AggregateBy.cs | 16 ++++++ SendGrid/SendGrid/Model/GlobalStat.cs | 14 ++++++ SendGrid/SendGrid/Model/Metrics.cs | 55 +++++++++++++++++++++ SendGrid/SendGrid/Model/Stats.cs | 16 ++++++ SendGrid/SendGrid/Resources/GlobalStats.cs | 33 +++++++------ SendGrid/SendGrid/SendGrid.csproj | 5 ++ SendGrid/SendGrid/Utilities/Extensions.cs | 31 ++++++++++++ 9 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 SendGrid/SendGrid/Model/AggregateBy.cs create mode 100644 SendGrid/SendGrid/Model/GlobalStat.cs create mode 100644 SendGrid/SendGrid/Model/Metrics.cs create mode 100644 SendGrid/SendGrid/Model/Stats.cs create mode 100644 SendGrid/SendGrid/Utilities/Extensions.cs diff --git a/SendGrid/Example/Example.csproj b/SendGrid/Example/Example.csproj index 786d6fb25..77b7931b1 100644 --- a/SendGrid/Example/Example.csproj +++ b/SendGrid/Example/Example.csproj @@ -44,6 +44,9 @@ + + Example.Program + diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 447bd0478..a2bbebeae 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -1,8 +1,9 @@ -using System; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Mail; -using Newtonsoft.Json.Linq; namespace Example { @@ -22,7 +23,7 @@ private static void Main() GlobalSuppressions(); GlobalStats(); } - + private static void SendAsync(SendGrid.SendGridMessage message) { string apikey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY"); @@ -141,7 +142,7 @@ private static void UnsubscribeGroups() Console.WriteLine("Unsubscribe Group Deleted.\n\nPress any key to end."); Console.ReadKey(); } - + private static void Suppressions() { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); @@ -219,45 +220,31 @@ private static void GlobalSuppressions() private static void GlobalStats() { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + Console.WriteLine("\n***** GLOBAL STATS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey); // Global Stats provide all of your user’s email statistics for a given date range. - var startDate = "2015-11-01"; - HttpResponseMessage response = client.GlobalStats.Get(startDate).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and no end date.\n\nPress any key to continue."); - Console.ReadKey(); + var startDate = new DateTime(2015, 11, 01); + var stats = client.GlobalStats.GetAsync(startDate).Result; + Console.WriteLine("Number of stats with start date {0} and no end date: {1}", startDate.ToShortDateString(), stats.Length); - var endDate = "2015-12-01"; - response = client.GlobalStats.Get(startDate, endDate).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + ".\n\nPress any key to continue."); - Console.ReadKey(); + var endDate = new DateTime(2015, 12, 01); + stats = client.GlobalStats.GetAsync(startDate, endDate).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1}: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - var aggregatedBy = "day"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); - Console.ReadKey(); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Day).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by day: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - aggregatedBy = "week"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); - Console.ReadKey(); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Week).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by week: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - aggregatedBy = "month"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Month).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by month: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } - } } diff --git a/SendGrid/SendGrid/Model/AggregateBy.cs b/SendGrid/SendGrid/Model/AggregateBy.cs new file mode 100644 index 000000000..153b5f80b --- /dev/null +++ b/SendGrid/SendGrid/Model/AggregateBy.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace SendGrid.Model +{ + public enum AggregateBy + { + [Description("none")] + None, + [Description("day")] + Day, + [Description("week")] + Week, + [Description("month")] + Month + } +} diff --git a/SendGrid/SendGrid/Model/GlobalStat.cs b/SendGrid/SendGrid/Model/GlobalStat.cs new file mode 100644 index 000000000..8e2d49130 --- /dev/null +++ b/SendGrid/SendGrid/Model/GlobalStat.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; + +namespace SendGrid.Model +{ + public class GlobalStat + { + [JsonProperty("date")] + public DateTime Date { get; set; } + + [JsonProperty("stats")] + public Stat[] Stats { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Metrics.cs b/SendGrid/SendGrid/Model/Metrics.cs new file mode 100644 index 000000000..c25993532 --- /dev/null +++ b/SendGrid/SendGrid/Model/Metrics.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Metrics + { + [JsonProperty("blocks")] + public long Blocks { get; set; } + + [JsonProperty("bounce_drops")] + public long BounceDrops { get; set; } + + [JsonProperty("bounces")] + public long Bounces { get; set; } + + [JsonProperty("clicks")] + public long Clicks { get; set; } + + [JsonProperty("deferred")] + public long Deferred { get; set; } + + [JsonProperty("delivered")] + public long Delivered { get; set; } + + [JsonProperty("invalid_emails")] + public long InvalidEmails { get; set; } + + [JsonProperty("opens")] + public long Opens { get; set; } + + [JsonProperty("processed")] + public long Processed { get; set; } + + [JsonProperty("requests")] + public long Requests { get; set; } + + [JsonProperty("spam_report_drops")] + public long SpamReportDrops { get; set; } + + [JsonProperty("spam_reports")] + public long SpamReports { get; set; } + + [JsonProperty("unique_clicks")] + public long UniqueClicks { get; set; } + + [JsonProperty("unique_opens")] + public long UniqueOpens { get; set; } + + [JsonProperty("unsubscribe_drops")] + public long UnsubscribeDrops { get; set; } + + [JsonProperty("unsubscribes")] + public long Unsubscribes { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Stats.cs b/SendGrid/SendGrid/Model/Stats.cs new file mode 100644 index 000000000..3e32b7fe0 --- /dev/null +++ b/SendGrid/SendGrid/Model/Stats.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Stat + { + [JsonProperty("metrics")] + public Metrics Metrics { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/GlobalStats.cs b/SendGrid/SendGrid/Resources/GlobalStats.cs index 73f0b336b..590c36004 100644 --- a/SendGrid/SendGrid/Resources/GlobalStats.cs +++ b/SendGrid/SendGrid/Resources/GlobalStats.cs @@ -1,5 +1,7 @@ -using System; -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System; using System.Threading.Tasks; using System.Web; @@ -25,24 +27,23 @@ public GlobalStats(Client client, string endpoint = "v3/stats") /// /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/global.html /// - /// The starting date of the statistics to retrieve, formatted as YYYY-MM-DD. - /// The end date of the statistics to retrieve, formatted as YYYY-MM-DD. Defaults to today. + /// The starting date of the statistics to retrieve. + /// The end date of the statistics to retrieve. Defaults to today. /// How to group the statistics, must be day|week|month /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/global.html - public async Task Get(string startDate, string endDate = null, string aggregatedBy = null) + public async Task GetAsync(DateTime startDate, DateTime? endDate = null, AggregateBy aggregatedBy = AggregateBy.None) { var query = HttpUtility.ParseQueryString(string.Empty); - query["start_date"] = startDate; - if (endDate != null) - { - query["end_date"] = endDate; - } - if (aggregatedBy != null) - { - query["aggregated_by"] = aggregatedBy; - } - return await _client.Get(_endpoint + "?" + query); - } + query["start_date"] = startDate.ToString("yyyy-MM-dd"); + if (endDate.HasValue) query["end_date"] = endDate.Value.ToString("yyyy-MM-dd"); + if (aggregatedBy != AggregateBy.None) query["aggregated_by"] = aggregatedBy.GetDescription(); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + var responseContent = await response.Content.ReadAsStringAsync(); + var getStatsResult = JArray.Parse(responseContent).ToObject(); + return getStatsResult; + } } } \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index 2f5bcf68f..c76657cf3 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,12 +64,17 @@ + + + + + diff --git a/SendGrid/SendGrid/Utilities/Extensions.cs b/SendGrid/SendGrid/Utilities/Extensions.cs new file mode 100644 index 000000000..952a25675 --- /dev/null +++ b/SendGrid/SendGrid/Utilities/Extensions.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using System.Net.Http; + +namespace SendGrid.Utilities +{ + public static class Extensions + { + public static string GetDescription(this Enum value) + { + var fieldInfo = value.GetType().GetField(value.ToString()); + if (fieldInfo == null) return value.ToString(); + + var attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attributes == null || attributes.Length == 0) return value.ToString(); + + var descriptionAttribute = attributes[0] as DescriptionAttribute; + return (descriptionAttribute == null ? value.ToString() : descriptionAttribute.Description); + } + + public static void EnsureSuccess(this HttpResponseMessage response) + { + if (response.IsSuccessStatusCode) return; + + var content = response.Content.ReadAsStringAsync().Result; + if (response.Content != null) response.Content.Dispose(); + + throw new Exception(content); + } + } +} From 40fadc951a61f3af02918ac8687aad7ac4dc239b Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 3 Feb 2016 11:52:52 -0600 Subject: [PATCH 04/52] Strongly typed GlobalSuppressions --- SendGrid/Example/Program.cs | 46 +++++++------------ .../SendGrid/Resources/GlobalSuppressions.cs | 38 +++++++++++---- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index a2bbebeae..c967af094 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -181,40 +181,28 @@ private static void Suppressions() private static void GlobalSuppressions() { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); - // CHECK IF EMAIL IS ON THE GLOBAL SUPPRESSION LIST - var email = "elmer.thomas+test_global@gmail.com"; - HttpResponseMessage responseGetUnique = client.GlobalSuppressions.Get(email).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("Determines if the given email is listed on the Global Suppressions list. Press any key to continue."); - Console.ReadKey(); + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey); // ADD EMAILS TO THE GLOBAL SUPPRESSION LIST - string[] emails = { "example@example.com", "example2@example.com" }; - HttpResponseMessage responsePost = client.GlobalSuppressions.Post(emails).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails added to Global Suppression Group.\n\nPress any key to continue."); - Console.ReadKey(); + var emails = new[] { "example@example.com", "example2@example.com" }; + client.GlobalSuppressions.AddAsync(emails).Wait(); + Console.WriteLine("\nThe following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); + Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); + Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); // DELETE EMAILS FROM THE GLOBAL SUPPRESSION GROUP - Console.WriteLine("Deleting emails from Global Suppression Group, please wait."); - HttpResponseMessage responseDelete1 = client.GlobalSuppressions.Delete("example@example.com").Result; - Console.WriteLine(responseDelete1.StatusCode); - HttpResponseMessage responseDelete2 = client.GlobalSuppressions.Delete("example2@example.com").Result; - Console.WriteLine(responseDelete2.StatusCode); - HttpResponseMessage responseFinal = client.GlobalSuppressions.Get("example@example.com").Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - HttpResponseMessage responseFinal2 = client.GlobalSuppressions.Get("example2@example.com").Result; - Console.WriteLine(responseFinal2.StatusCode); - Console.WriteLine(responseFinal2.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails removed from Global Suppression Group.\n\nPress any key to end."); + client.GlobalSuppressions.RemoveAsync(emails[0]).Wait(); + Console.WriteLine("{0} has been removed from the global suppression list", emails[0]); + client.GlobalSuppressions.RemoveAsync(emails[1]).Wait(); + Console.WriteLine("{0} has been removed from the global suppression list", emails[1]); + + Console.WriteLine("Is {0} unsubscribed (should be false): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); + Console.WriteLine("Is {0} unsubscribed (should be false): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } diff --git a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs index 36fcd1852..a96723792 100644 --- a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs +++ b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs @@ -1,6 +1,8 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -26,9 +28,24 @@ public GlobalSuppressions(Client client, string endpoint = "v3/asm/suppressions/ /// /// email address to check /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Get(string email) + public async Task IsUnsubscribedAsync(string email) { - return await _client.Get(_endpoint + "/" + email); + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + + // If the email address is on the global suppression list, the response will look like this: + // { + // "recipient_email": "{email}" + // } + // If the email address is not on the global suppression list, the response will be empty + // + // Therefore, we check for the presence of the 'recipient_email' to indicate if the email + // address is on the global suppression list or not. + + var responseContent = await response.Content.ReadAsStringAsync(); + var dynamicObject = JObject.Parse(responseContent); + var propertyDictionary = (IDictionary)dynamicObject; + return propertyDictionary.ContainsKey("recipient_email"); } /// @@ -36,12 +53,12 @@ public async Task Get(string email) /// /// Array of email addresses to add to the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Post(string[] emails) + public async Task AddAsync(IEnumerable emails) { - JArray receipient_emails = new JArray(); - foreach (string email in emails) { receipient_emails.Add(email); } + var receipient_emails = new JArray(emails.ToArray()); var data = new JObject(new JProperty("recipient_emails", receipient_emails)); - return await _client.Post(_endpoint, data); + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); } /// @@ -49,9 +66,10 @@ public async Task Post(string[] emails) /// /// email address to be removed from the global suppressions group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Delete(string email) + public async Task RemoveAsync(string email) { - return await _client.Delete(_endpoint + "/" + email); + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); } } } \ No newline at end of file From 4659022707536eedc7cf3bc8816d6f1073f6f0d8 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 3 Feb 2016 15:50:17 -0600 Subject: [PATCH 05/52] Strongly typed UnsubscribeGroups Also allow the HttpClient to be injected (this is useful for mocking and for using Fiddler) --- SendGrid/Example/Program.cs | 99 ++++++++++--------- SendGrid/SendGrid/Client.cs | 94 +++++++++--------- SendGrid/SendGrid/Model/SuppressionGroup.cs | 19 ++++ .../SendGrid/Resources/GlobalSuppressions.cs | 3 +- .../SendGrid/Resources/UnsubscribeGroups.cs | 76 ++++++++++---- SendGrid/SendGrid/SendGrid.csproj | 1 + 6 files changed, 180 insertions(+), 112 deletions(-) create mode 100644 SendGrid/SendGrid/Model/SuppressionGroup.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index c967af094..6983c436e 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -2,8 +2,10 @@ using SendGrid.Model; using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Net.Mail; +using System.Net.Security; namespace Example { @@ -11,17 +13,36 @@ internal class Program { private static void Main() { + // Do you want to proxy requests through fiddler? + var useFiddler = false; + + if (useFiddler) + { + // This is necessary to ensure HTTPS traffic can be proxied through Fiddler without any certificate validation error. + // This is fine for testing but not advisable in production + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); + } + + // Direct all traffic through fiddler running on the local machine + var httpClient = new HttpClient( + new HttpClientHandler() + { + Proxy = new WebProxy("http://localhost:8888"), + UseProxy = useFiddler + } + ); + // Test sending email var to = "example@example.com"; var from = "example@example.com"; var fromName = "Jane Doe"; SendEmail(to, from, fromName); // Test viewing, creating, modifying and deleting API keys through our v3 Web API - ApiKeys(); - UnsubscribeGroups(); - Suppressions(); - GlobalSuppressions(); - GlobalStats(); + ApiKeys(httpClient); + UnsubscribeGroups(httpClient); + Suppressions(httpClient); + GlobalSuppressions(httpClient); + GlobalStats(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -62,10 +83,10 @@ private static void SendEmail(string to, string from, string fromName) SendAsync(myMessage); } - private static void ApiKeys() + private static void ApiKeys(HttpClient httpClient) { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // GET API KEYS HttpResponseMessage responseGet = client.ApiKeys.Get().Result; @@ -102,48 +123,38 @@ private static void ApiKeys() Console.ReadKey(); } - private static void UnsubscribeGroups() + private static void UnsubscribeGroups(HttpClient httpClient) { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + Console.WriteLine("\n***** UNSUBSCRIBE GROUPS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + // CREATE A NEW SUPPRESSION GROUP + var newGroup = client.UnsubscribeGroups.CreateAsync("New group", "This is a new group for testing purposes", false).Result; + Console.WriteLine("Unique ID of the new suppresion group: {0}", newGroup.Id); + + // UPDATE A SUPPRESSION GROUP + var updatedGroup = client.UnsubscribeGroups.UpdateAsync(newGroup.Id, "This is the updated name").Result; + Console.WriteLine("Suppresion group {0} updated", updatedGroup.Id); // GET UNSUBSCRIBE GROUPS - HttpResponseMessage responseGet = client.UnsubscribeGroups.Get().Result; - Console.WriteLine(responseGet.StatusCode); - Console.WriteLine(responseGet.Content.ReadAsStringAsync().Result); - Console.WriteLine("These are your current Unsubscribe Groups. Press any key to continue."); - Console.ReadKey(); + var groups = client.UnsubscribeGroups.GetAllAsync().Result; + Console.WriteLine("There are {0} suppresion groups", groups.Length); // GET A PARTICULAR UNSUBSCRIBE GROUP - int unsubscribeGroupID = 69; - HttpResponseMessage responseGetUnique = client.UnsubscribeGroups.Get(unsubscribeGroupID).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("This is an Unsubscribe Group with ID: " + unsubscribeGroupID.ToString() + ".\n\nPress any key to continue."); - Console.ReadKey(); - - // POST UNSUBSCRIBE GROUP - HttpResponseMessage responsePost = client.UnsubscribeGroups.Post("C Sharp Unsubscribes", "Testing the C Sharp Library", false).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - var unsubscribeGroupId = jsonObject.id.ToString(); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Unsubscribe Group created.\n\nPress any key to continue."); - Console.ReadKey(); + var group = client.UnsubscribeGroups.GetAsync(newGroup.Id).Result; + Console.WriteLine("Retrieved unsubscribe group {0}: {1}", group.Id, group.Name); // DELETE UNSUBSCRIBE GROUP - Console.WriteLine("Deleting Unsubscribe Group, please wait."); - HttpResponseMessage responseDelete = client.UnsubscribeGroups.Delete(unsubscribeGroupId).Result; - Console.WriteLine(responseDelete.StatusCode); - HttpResponseMessage responseFinal = client.UnsubscribeGroups.Get().Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - Console.WriteLine("Unsubscribe Group Deleted.\n\nPress any key to end."); + client.UnsubscribeGroups.DeleteAsync(newGroup.Id).Wait(); + Console.WriteLine("Suppression group {0} deleted", newGroup.Id); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } - private static void Suppressions() + private static void Suppressions(HttpClient httpClient) { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey); @@ -179,17 +190,17 @@ private static void Suppressions() Console.ReadKey(); } - private static void GlobalSuppressions() + private static void GlobalSuppressions(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // ADD EMAILS TO THE GLOBAL SUPPRESSION LIST var emails = new[] { "example@example.com", "example2@example.com" }; client.GlobalSuppressions.AddAsync(emails).Wait(); - Console.WriteLine("\nThe following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); + Console.WriteLine("The following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); @@ -206,12 +217,12 @@ private static void GlobalSuppressions() Console.ReadKey(); } - private static void GlobalStats() + private static void GlobalStats(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL STATS *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // Global Stats provide all of your user’s email statistics for a given date range. var startDate = new DateTime(2015, 11, 01); diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 709ea254f..968f4687a 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -13,34 +13,44 @@ namespace SendGrid public class Client { private string _apiKey; - public APIKeys ApiKeys; - public UnsubscribeGroups UnsubscribeGroups; - public Suppressions Suppressions; - public GlobalSuppressions GlobalSuppressions; - public GlobalStats GlobalStats; - public string Version; private Uri _baseUri; + private HttpClient _httpClient; private const string MediaType = "application/json"; private enum Methods { GET, POST, PATCH, DELETE } + public APIKeys ApiKeys { get; private set; } + public UnsubscribeGroups UnsubscribeGroups { get; private set; } + public Suppressions Suppressions { get; private set; } + public GlobalSuppressions GlobalSuppressions { get; private set; } + public GlobalStats GlobalStats { get; private set; } + public string Version { get; private set; } + /// /// Create a client that connects to the SendGrid Web API /// /// Your SendGrid API Key /// Base SendGrid API Uri - public Client(string apiKey, string baseUri = "https://api.sendgrid.com/") + public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpClient httpClient = null) { _baseUri = new Uri(baseUri); _apiKey = apiKey; + Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); ApiKeys = new APIKeys(this); UnsubscribeGroups = new UnsubscribeGroups(this); Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + + _httpClient = httpClient ?? new HttpClient(); + _httpClient.BaseAddress = _baseUri; + _httpClient.DefaultRequestHeaders.Accept.Clear(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaType)); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", string.Format("sendgrid/{0};csharp", Version)); } /// @@ -78,50 +88,40 @@ private async Task RequestAsync(Methods method, string endp /// An asyncronous task private async Task RequestAsync(Methods method, string endpoint, StringContent content) { - using (var client = new HttpClient()) + try { - try + var methodAsString = ""; + switch (method) { - client.BaseAddress = _baseUri; - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaType)); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "sendgrid/" + Version + ";csharp"); - - var methodAsString = ""; - switch (method) - { - case Methods.GET: methodAsString = "GET"; break; - case Methods.POST: methodAsString = "POST"; break; - case Methods.PATCH: methodAsString = "PATCH"; break; - case Methods.DELETE: methodAsString = "DELETE"; break; - default: - var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; - var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) - { - Content = new StringContent(message) - }; - return response; - } - - var postRequest = new HttpRequestMessage() - { - Method = new HttpMethod(methodAsString), - RequestUri = new Uri(_baseUri + endpoint), - Content = content - }; - return await client.SendAsync(postRequest); - + case Methods.GET: methodAsString = "GET"; break; + case Methods.POST: methodAsString = "POST"; break; + case Methods.PATCH: methodAsString = "PATCH"; break; + case Methods.DELETE: methodAsString = "DELETE"; break; + default: + var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; + var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) + { + Content = new StringContent(message) + }; + return response; } - catch (Exception ex) + + var httpRequest = new HttpRequestMessage() { - var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); - var response = new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent(message) - }; - return response; - } + Method = new HttpMethod(methodAsString), + RequestUri = new Uri(_baseUri + endpoint), + Content = content + }; + return await _httpClient.SendAsync(httpRequest); + } + catch (Exception ex) + { + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent(message) + }; + return response; } } diff --git a/SendGrid/SendGrid/Model/SuppressionGroup.cs b/SendGrid/SendGrid/Model/SuppressionGroup.cs new file mode 100644 index 000000000..26e3b72a1 --- /dev/null +++ b/SendGrid/SendGrid/Model/SuppressionGroup.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class SuppressionGroup + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("is_default")] + public bool IsDefault { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs index a96723792..c31cd716f 100644 --- a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs +++ b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs @@ -55,8 +55,7 @@ public async Task IsUnsubscribedAsync(string email) /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html public async Task AddAsync(IEnumerable emails) { - var receipient_emails = new JArray(emails.ToArray()); - var data = new JObject(new JProperty("recipient_emails", receipient_emails)); + var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); var response = await _client.Post(_endpoint, data); response.EnsureSuccess(); } diff --git a/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs b/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs index db8e4d228..51b55f528 100644 --- a/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs +++ b/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs @@ -1,6 +1,7 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -25,36 +26,72 @@ public UnsubscribeGroups(Client client, string endpoint = "v3/asm/groups") /// Retrieve all suppression groups associated with the user. /// /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Get() + public async Task GetAllAsync() { - return await _client.Get(_endpoint); + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var groups = JArray.Parse(responseContent).ToObject(); + return groups; } /// /// Get information on a single suppression group. /// - /// ID of the suppression group to delete + /// ID of the suppression group to delete /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Get(int unsubscribeGroupId) + public async Task GetAsync(int groupId) { - return await _client.Get(_endpoint + "/" + unsubscribeGroupId); + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, groupId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; } /// /// Create a new suppression group. /// - /// The name of the new suppression group - /// A description of the suppression group - /// Default value is false + /// The name of the new suppression group + /// A description of the suppression group + /// Default value is false + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html + public async Task CreateAsync(string name, string description, bool isDefault) + { + var data = new JObject() + { + { "name", name }, + { "description", description }, + { "is_default", isDefault } + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; + } + + /// + /// Update an existing suppression group. + /// + /// The name of the new suppression group + /// A description of the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Post(string unsubscribeGroupName, - string unsubscribeGroupDescription, - bool unsubscribeGroupIsDefault) + public async Task UpdateAsync(int groupId, string name = null, string description = null) { - var data = new JObject {{"name", unsubscribeGroupName}, - {"description", unsubscribeGroupDescription}, - {"is_default", unsubscribeGroupIsDefault}}; - return await _client.Post(_endpoint, data); + var data = new JObject(); + if (name != null) data.Add("name", name); + if (description != null) data.Add("description", description); + + var response = await _client.Patch(string.Format("{0}/{1}", _endpoint, groupId), data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; } /// @@ -62,9 +99,10 @@ public async Task Get(int unsubscribeGroupId) /// /// ID of the suppression group to delete /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Delete(string unsubscribeGroupId) + public async Task DeleteAsync(int groupId) { - return await _client.Delete(_endpoint + "/" + unsubscribeGroupId); + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, groupId)); + response.EnsureSuccess(); } } } \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index c76657cf3..f0a37c1be 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,6 +64,7 @@ + From 602a9b4751cb8e5b413ec10c4c7136dc9be04c94 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Thu, 4 Feb 2016 12:30:13 -0600 Subject: [PATCH 06/52] Strongly typed 'Suppression' --- SendGrid/Example/Program.cs | 75 +++++++++------------ SendGrid/SendGrid/Resources/Suppressions.cs | 47 +++++++++---- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 6983c436e..7fee1e530 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -31,7 +31,7 @@ private static void Main() UseProxy = useFiddler } ); - + // Test sending email var to = "example@example.com"; var from = "example@example.com"; @@ -40,7 +40,6 @@ private static void Main() // Test viewing, creating, modifying and deleting API keys through our v3 Web API ApiKeys(httpClient); UnsubscribeGroups(httpClient); - Suppressions(httpClient); GlobalSuppressions(httpClient); GlobalStats(httpClient); } @@ -132,20 +131,48 @@ private static void UnsubscribeGroups(HttpClient httpClient) // CREATE A NEW SUPPRESSION GROUP var newGroup = client.UnsubscribeGroups.CreateAsync("New group", "This is a new group for testing purposes", false).Result; - Console.WriteLine("Unique ID of the new suppresion group: {0}", newGroup.Id); - + Console.WriteLine("Unique ID of the new unsubscribe group: {0}", newGroup.Id); + // UPDATE A SUPPRESSION GROUP var updatedGroup = client.UnsubscribeGroups.UpdateAsync(newGroup.Id, "This is the updated name").Result; - Console.WriteLine("Suppresion group {0} updated", updatedGroup.Id); + Console.WriteLine("Unsubscribe group {0} updated", updatedGroup.Id); // GET UNSUBSCRIBE GROUPS var groups = client.UnsubscribeGroups.GetAllAsync().Result; - Console.WriteLine("There are {0} suppresion groups", groups.Length); + Console.WriteLine("There are {0} unsubscribe groups", groups.Length); // GET A PARTICULAR UNSUBSCRIBE GROUP var group = client.UnsubscribeGroups.GetAsync(newGroup.Id).Result; Console.WriteLine("Retrieved unsubscribe group {0}: {1}", group.Id, group.Name); + // ADD A FEW ADDRESSES TO UNSUBSCRIBE GROUP + client.Suppressions.AddAddressToUnsubscribeGroupAsync(group.Id, "test1@example.com").Wait(); + Console.WriteLine("Added test1@example.com to unsubscribe group {0}", group.Id); + client.Suppressions.AddAddressToUnsubscribeGroupAsync(group.Id, "test2@example.com").Wait(); + Console.WriteLine("Added test2@example.com to unsubscribe group {0}", group.Id); + + // GET THE ADDRESSES IN A GROUP + var unsubscribedAddresses = client.Suppressions.GetUnsubscribedAddressesAsync(group.Id).Result; + Console.WriteLine("There are {0} unsubscribed addresses in group {1}", unsubscribedAddresses.Length, group.Id); + + // REMOVE ALL ADDRESSES FROM UNSUBSCRIBE GROUP + foreach (var address in unsubscribedAddresses) + { + client.Suppressions.RemoveAddressFromSuppressionGroupAsync(group.Id, address).Wait(); + Console.WriteLine("{0} removed from unsubscribe group {1}", address, group.Id); + } + + // MAKE SURE THERE ARE NO ADDRESSES IN THE GROUP + unsubscribedAddresses = client.Suppressions.GetUnsubscribedAddressesAsync(group.Id).Result; + if (unsubscribedAddresses.Length == 0) + { + Console.WriteLine("As expected, there are no more addresses in group {0}", group.Id); + } + else + { + Console.WriteLine("We expected the group {1} to be empty but instead we found {0} unsubscribed addresses.", unsubscribedAddresses.Length, group.Id); + } + // DELETE UNSUBSCRIBE GROUP client.UnsubscribeGroups.DeleteAsync(newGroup.Id).Wait(); Console.WriteLine("Suppression group {0} deleted", newGroup.Id); @@ -154,42 +181,6 @@ private static void UnsubscribeGroups(HttpClient httpClient) Console.ReadKey(); } - private static void Suppressions(HttpClient httpClient) - { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); - - // GET SUPPRESSED ADDRESSES FOR A GIVEN GROUP - int groupID = 69; - HttpResponseMessage responseGetUnique = client.Suppressions.Get(groupID).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("These are the suppressed emails with group ID: " + groupID.ToString() + ". Press any key to continue."); - Console.ReadKey(); - - // ADD EMAILS TO A SUPPRESSION GROUP - string[] emails = { "example@example.com", "example2@example.com" }; - HttpResponseMessage responsePost = client.Suppressions.Post(groupID, emails).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails added to Suppression Group:" + groupID.ToString() + ".\n\nPress any key to continue."); - Console.ReadKey(); - - // DELETE EMAILS FROM A SUPPRESSION GROUP - Console.WriteLine("Deleting emails from Suppression Group, please wait."); - HttpResponseMessage responseDelete1 = client.Suppressions.Delete(groupID, "example@example.com").Result; - Console.WriteLine(responseDelete1.StatusCode); - HttpResponseMessage responseDelete2 = client.Suppressions.Delete(groupID, "example2@example.com").Result; - Console.WriteLine(responseDelete2.StatusCode); - HttpResponseMessage responseFinal = client.Suppressions.Get(groupID).Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails removed from Suppression Group" + groupID.ToString() + "Deleted.\n\nPress any key to end."); - Console.ReadKey(); - } - private static void GlobalSuppressions(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); diff --git a/SendGrid/SendGrid/Resources/Suppressions.cs b/SendGrid/SendGrid/Resources/Suppressions.cs index 91bb38d60..523b52092 100644 --- a/SendGrid/SendGrid/Resources/Suppressions.cs +++ b/SendGrid/SendGrid/Resources/Suppressions.cs @@ -1,6 +1,8 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -26,9 +28,27 @@ public Suppressions(Client client, string endpoint = "v3/asm/groups") /// /// ID of the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Get(int groupId) + public async Task GetUnsubscribedAddressesAsync(int groupId) { - return await _client.Get(_endpoint + "/" + groupId.ToString() + "/suppressions"); + var response = await _client.Get(string.Format("{0}/{1}/suppressions", _endpoint, groupId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var suppressedAddresses = JArray.Parse(responseContent).ToObject(); + return suppressedAddresses; + } + + /// + /// Add recipient address to the suppressions list for a given group. + /// + /// If the group has been deleted, this request will add the address to the global suppression. + /// + /// ID of the suppression group + /// Email address to add to the suppression group + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html + public async Task AddAddressToUnsubscribeGroupAsync(int groupId, string email) + { + await AddAddressToUnsubscribeGroupAsync(groupId, new[] { email }); } /// @@ -37,24 +57,25 @@ public async Task Get(int groupId) /// If the group has been deleted, this request will add the address to the global suppression. /// /// ID of the suppression group - /// Array of email addresses to add to the suppression group + /// Email addresses to add to the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Post(int groupId, string[] emails) + public async Task AddAddressToUnsubscribeGroupAsync(int groupId, IEnumerable emails) { - JArray receipient_emails = new JArray(); - foreach (string email in emails) { receipient_emails.Add(email); } - var data = new JObject(new JProperty("recipient_emails", receipient_emails)); - return await _client.Post(_endpoint + "/" + groupId.ToString() + "/suppressions", data); + var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); + var response = await _client.Post(string.Format("{0}/{1}/suppressions", _endpoint, groupId), data); + response.EnsureSuccess(); } /// - /// Delete a suppression group. + /// Delete a recipient email from the suppressions list for a group. /// /// ID of the suppression group to delete + /// Email address to remove from the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Delete(int groupId, string email) + public async Task RemoveAddressFromSuppressionGroupAsync(int groupId, string email) { - return await _client.Delete(_endpoint + "/" + groupId.ToString() + "/suppressions/" + email); + var response = await _client.Delete(string.Format("{0}/{1}/suppressions/{2}", _endpoint, groupId, email)); + response.EnsureSuccess(); } } } \ No newline at end of file From fb10162d06739aa5bf03cc1d3493d9d568c8b362 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Fri, 5 Feb 2016 11:16:26 -0600 Subject: [PATCH 07/52] Add overload Client.Post method to accept a JArray --- SendGrid/SendGrid/Client.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 968f4687a..e4e4dfe7b 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -140,6 +140,14 @@ public async Task Post(string endpoint, JObject data) return await RequestAsync(Methods.POST, endpoint, data); } + /// Resource endpoint, do not prepend slash + /// An JArray representing the resource's data + /// The resulting message from the API call + public async Task Post(string endpoint, JArray data) + { + return await RequestAsync(Methods.POST, endpoint, data); + } + /// Resource endpoint, do not prepend slash /// An optional JArray representing the resource's data /// The resulting message from the API call From 35880a9287c220555db185f2089fc64af09fa82d Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 14:39:29 -0600 Subject: [PATCH 08/52] Strongly typed Bounces Added overloaded Client.Delete methods to accept JObject in addition to JArray Added EpochConverter to convert Unix time to .NET DateTime when parsing JSON Added convenient extension methods to convert unix time to .NET DateTime --- SendGrid/SendGrid/Client.cs | 17 ++++ SendGrid/SendGrid/Model/Bounce.cs | 22 +++++ SendGrid/SendGrid/Resources/Bounces.cs | 91 +++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 3 + SendGrid/SendGrid/Utilities/EpochConverter.cs | 30 ++++++ SendGrid/SendGrid/Utilities/Extensions.cs | 12 +++ 6 files changed, 175 insertions(+) create mode 100644 SendGrid/SendGrid/Model/Bounce.cs create mode 100644 SendGrid/SendGrid/Resources/Bounces.cs create mode 100644 SendGrid/SendGrid/Utilities/EpochConverter.cs diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index e4e4dfe7b..995440486 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -26,6 +26,7 @@ private enum Methods public Suppressions Suppressions { get; private set; } public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } + public Bounces Bounces { get; private set; } public string Version { get; private set; } /// @@ -44,6 +45,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + Bounces = new Bounces(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; @@ -148,6 +150,21 @@ public async Task Post(string endpoint, JArray data) return await RequestAsync(Methods.POST, endpoint, data); } + /// Resource endpoint, do not prepend slash + /// The resulting message from the API call + public async Task Delete(string endpoint) + { + return await RequestAsync(Methods.DELETE, endpoint, (StringContent)null); + } + + /// Resource endpoint, do not prepend slash + /// An optional JObject representing the resource's data + /// The resulting message from the API call + public async Task Delete(string endpoint, JObject data = null) + { + return await RequestAsync(Methods.DELETE, endpoint, data); + } + /// Resource endpoint, do not prepend slash /// An optional JArray representing the resource's data /// The resulting message from the API call diff --git a/SendGrid/SendGrid/Model/Bounce.cs b/SendGrid/SendGrid/Model/Bounce.cs new file mode 100644 index 000000000..58c5cc884 --- /dev/null +++ b/SendGrid/SendGrid/Model/Bounce.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using SendGrid.Utilities; +using System; + +namespace SendGrid.Model +{ + public class Bounce + { + [JsonProperty("created")] + [JsonConverter(typeof(EpochConverter))] + public DateTime CreatedOn { get; set; } + + [JsonProperty("email")] + public string EmailAddress { get; set; } + + [JsonProperty("reason")] + public string Reason { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Bounces.cs b/SendGrid/SendGrid/Resources/Bounces.cs new file mode 100644 index 000000000..3f90da452 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Bounces.cs @@ -0,0 +1,91 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Bounces + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Bounces object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Bounces(Client client, string endpoint = "v3/suppression/bounces") + { + _endpoint = endpoint; + _client = client; + } + + /// + /// Get a list of bounces + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task GetAsync(DateTime? start = null, DateTime? end = null) + { + var query = HttpUtility.ParseQueryString(string.Empty); + if (start.HasValue) query["start_time"] = start.Value.ToUnixTime().ToString(); + if (end.HasValue) query["end_time"] = end.Value.ToUnixTime().ToString(); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bounces = JArray.Parse(responseContent).ToObject(); + return bounces; + } + + /// + /// Get a list of bounces for a given email address + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task GetAsync(string email) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bounces = JArray.Parse(responseContent).ToObject(); + return bounces; + } + /// + /// Delete all bounces + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAllAsync() + { + var response = await _client.Delete(_endpoint); + response.EnsureSuccess(); + } + + /// + /// Delete bounces for a specified group of email addresses + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAsync(IEnumerable emails) + { + var data = new JObject(new JProperty("emails", new JArray(emails.ToArray()))); + var response = await _client.Delete(_endpoint, data); + response.EnsureSuccess(); + } + + /// + /// Delete bounces for a specified email address + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAsync(string email) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index f0a37c1be..ca19567cf 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,17 +64,20 @@ + + + diff --git a/SendGrid/SendGrid/Utilities/EpochConverter.cs b/SendGrid/SendGrid/Utilities/EpochConverter.cs new file mode 100644 index 000000000..138467be8 --- /dev/null +++ b/SendGrid/SendGrid/Utilities/EpochConverter.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace SendGrid.Utilities +{ + public class EpochConverter : DateTimeConverterBase + { + private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) return; + + var secondsSinceEpoch = ((DateTime)value).ToUnixTime(); + serializer.Serialize(writer, secondsSinceEpoch); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) return null; + return _epoch.AddSeconds((long)reader.Value); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/Utilities/Extensions.cs b/SendGrid/SendGrid/Utilities/Extensions.cs index 952a25675..c4fb86674 100644 --- a/SendGrid/SendGrid/Utilities/Extensions.cs +++ b/SendGrid/SendGrid/Utilities/Extensions.cs @@ -6,6 +6,18 @@ namespace SendGrid.Utilities { public static class Extensions { + private static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(this long unixTime) + { + return EPOCH.AddSeconds(unixTime); + } + + public static long ToUnixTime(this DateTime date) + { + return Convert.ToInt64((date.ToUniversalTime() - EPOCH).TotalSeconds); + } + public static string GetDescription(this Enum value) { var fieldInfo = value.GetType().GetField(value.ToString()); From 8e5f0d4d3be7d9a1f8bda03fc291c14de45fe466 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 16:30:23 -0600 Subject: [PATCH 09/52] Strongly typed custom fields --- SendGrid/SendGrid/Client.cs | 2 ++ SendGrid/SendGrid/Model/CustomField.cs | 10 ++++++++++ SendGrid/SendGrid/Model/Field.cs | 15 +++++++++++++++ SendGrid/SendGrid/Model/FieldType.cs | 14 ++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 4 ++++ 5 files changed, 45 insertions(+) create mode 100644 SendGrid/SendGrid/Model/CustomField.cs create mode 100644 SendGrid/SendGrid/Model/Field.cs create mode 100644 SendGrid/SendGrid/Model/FieldType.cs diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 995440486..b614ece6f 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -27,6 +27,7 @@ private enum Methods public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } public Bounces Bounces { get; private set; } + public CustomFields CustomFields { get; private set; } public string Version { get; private set; } /// @@ -46,6 +47,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); Bounces = new Bounces(this); + CustomFields = new CustomFields(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/CustomField.cs b/SendGrid/SendGrid/Model/CustomField.cs new file mode 100644 index 000000000..842952cbe --- /dev/null +++ b/SendGrid/SendGrid/Model/CustomField.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class CustomField : Field + { + [JsonProperty("id")] + public int Id { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Field.cs b/SendGrid/SendGrid/Model/Field.cs new file mode 100644 index 000000000..1dadf5f8f --- /dev/null +++ b/SendGrid/SendGrid/Model/Field.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace SendGrid.Model +{ + public class Field + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] + public FieldType Type { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/FieldType.cs b/SendGrid/SendGrid/Model/FieldType.cs new file mode 100644 index 000000000..300353e7c --- /dev/null +++ b/SendGrid/SendGrid/Model/FieldType.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace SendGrid.Model +{ + public enum FieldType + { + [EnumMember(Value = "date")] + Date, + [EnumMember(Value = "text")] + Text, + [EnumMember(Value = "number")] + Number + } +} diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index ca19567cf..fdef76e49 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,6 +64,9 @@ + + + @@ -72,6 +75,7 @@ + From b4e1f6740dc18df012091f535375a3eba4b94aa5 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 16:32:39 -0600 Subject: [PATCH 10/52] Display the underlying error message when exception occurs --- SendGrid/SendGrid/Client.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index b614ece6f..115511b3c 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -27,7 +27,9 @@ private enum Methods public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } public Bounces Bounces { get; private set; } + public User User { get; private set; } public CustomFields CustomFields { get; private set; } + public Contacts Contacts { get; private set; } public string Version { get; private set; } /// @@ -46,6 +48,8 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + User = new User(this); + Contacts = new Contacts(this); Bounces = new Bounces(this); CustomFields = new CustomFields(this); @@ -120,7 +124,7 @@ private async Task RequestAsync(Methods method, string endp } catch (Exception ex) { - var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.GetBaseException().Message); var response = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(message) From 5e6b31c381a7ba769907c3a17063b4afbe517a13 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 10:47:39 -0600 Subject: [PATCH 11/52] Added missing reference to system serialization --- SendGrid/SendGrid/SendGrid.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index fdef76e49..bb98e90f4 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -53,6 +53,7 @@ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True + From 68da11f7f0885594de9cd908c9bf8bc546e46b0c Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 15:53:25 -0600 Subject: [PATCH 12/52] Strongly typed Lists --- SendGrid/Example/Program.cs | 33 +++++ SendGrid/SendGrid/Client.cs | 2 + SendGrid/SendGrid/Model/List.cs | 16 +++ SendGrid/SendGrid/Resources/Lists.cs | 184 +++++++++++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 2 + 5 files changed, 237 insertions(+) create mode 100644 SendGrid/SendGrid/Model/List.cs create mode 100644 SendGrid/SendGrid/Resources/Lists.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 7fee1e530..9b7c9733a 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -42,6 +42,7 @@ private static void Main() UnsubscribeGroups(httpClient); GlobalSuppressions(httpClient); GlobalStats(httpClient); + Lists(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -236,5 +237,37 @@ private static void GlobalStats(HttpClient httpClient) Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } + + private static void Lists(HttpClient httpClient) + { + Console.WriteLine("\n***** LISTS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + var firstList = client.Lists.CreateAsync("My first list").Result; + Console.WriteLine("List '{0}' created. Id: {1}", firstList.Name, firstList.Id); + + var secondList = client.Lists.CreateAsync("My second list").Result; + Console.WriteLine("List '{0}' created. Id: {1}", secondList.Name, secondList.Id); + + client.Lists.UpdateAsync(firstList.Id, "New name").Wait(); + Console.WriteLine("List '{0}' updated", firstList.Id); + + var lists = client.Lists.GetAllAsync().Result; + Console.WriteLine("All lists retrieved. There are {0} lists", lists.Length); + + client.Lists.DeleteAsync(firstList.Id).Wait(); + Console.WriteLine("List {0} deleted", firstList.Id); + + client.Lists.DeleteAsync(secondList.Id).Wait(); + Console.WriteLine("List {0} deleted", secondList.Id); + + lists = client.Lists.GetAllAsync().Result; + Console.WriteLine("All lists retrieved. There are {0} lists", lists.Length); + + Console.WriteLine("\n\nPress any key to continue"); + Console.ReadKey(); + } } } diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 115511b3c..259dc00be 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -30,6 +30,7 @@ private enum Methods public User User { get; private set; } public CustomFields CustomFields { get; private set; } public Contacts Contacts { get; private set; } + public Lists Lists { get; private set; } public string Version { get; private set; } /// @@ -52,6 +53,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Contacts = new Contacts(this); Bounces = new Bounces(this); CustomFields = new CustomFields(this); + Lists = new Lists(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/List.cs b/SendGrid/SendGrid/Model/List.cs new file mode 100644 index 000000000..7e24ea5fb --- /dev/null +++ b/SendGrid/SendGrid/Model/List.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class List + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("recipient_count")] + public long RecipientCount { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Lists.cs b/SendGrid/SendGrid/Resources/Lists.cs new file mode 100644 index 000000000..b2ba60da3 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Lists.cs @@ -0,0 +1,184 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Lists + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Lists object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/contactdb.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Lists(Client client, string endpoint = "v3/contactdb/lists") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(string name) + { + var data = new JObject() + { + new JProperty("name", name) + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bulkUpsertResult = JObject.Parse(responseContent).ToObject(); + return bulkUpsertResult; + } + + public async Task GetAllAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "lists": [ + // { + // "id": 1, + // "name": "the jones", + // "recipient_count": 1 + // } + // ] + //} + // We use a dynamic object to get rid of the 'lists' property and simply return an array of lists + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.lists; + + var lists = dynamicArray.ToObject(); + return lists; + } + + public async Task DeleteAsync(long listId) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, listId)); + response.EnsureSuccess(); + } + + public async Task DeleteAsync(IEnumerable recipientIds) + { + var data = new JArray(recipientIds.ToArray()); + var response = await _client.Delete(_endpoint, data); + response.EnsureSuccess(); + } + + public async Task GetAsync(int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "lists": [ + // { + // "id": 1, + // "name": "the jones", + // "recipient_count": 1 + // } + // ] + //} + // We use a dynamic object to get rid of the 'lists' property and simply return an array of lists + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.lists; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task GetAsync(long listId) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, listId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var list = JObject.Parse(responseContent).ToObject(); + return list; + } + + public async Task UpdateAsync(long listId, string name) + { + var data = new JObject() + { + new JProperty("name", name) + }; + var response = await _client.Patch(string.Format("{0}/{1}", _endpoint, listId), data); + response.EnsureSuccess(); + } + + public async Task GetRecipientsAsync(long listId, int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}/{1}/recipients?{2}", _endpoint, listId, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipients": [ + // { + // "created_at": 1422395108, + // "email": "e@example.com", + // "first_name": "Ed", + // "id": "YUBh", + // "last_clicked": null, + // "last_emailed": null, + // "last_name": null, + // "last_opened": null, + // "updated_at": 1422395108 + // } + // ] + // } + // We use a dynamic object to get rid of the 'recipients' property and simply return an array of recipients + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.recipients; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task AddRecipientAsync(long listId, string recipientId) + { + var response = await _client.Post(string.Format("{0}/{1}/recipients/{2}", _endpoint, listId, recipientId), (JObject)null); + response.EnsureSuccess(); + } + + public async Task RemoveRecipientAsync(long listId, string recipientId) + { + var response = await _client.Delete(string.Format("{0}/{1}/recipients/{2}", _endpoint, listId, recipientId)); + response.EnsureSuccess(); + } + + public async Task AddRecipientsAsync(long listId, IEnumerable recipientIds) + { + var data = new JArray(recipientIds.ToArray()); + var response = await _client.Post(string.Format("{0}/{1}/recipients", _endpoint, listId), data); + response.EnsureSuccess(); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index bb98e90f4..877f2456f 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -69,6 +69,7 @@ + @@ -79,6 +80,7 @@ + From 3a01e7963b865abf62813ea0ef5b6a644daeb4f0 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 16:46:23 -0600 Subject: [PATCH 13/52] Added strongly typed Custom Fields --- SendGrid/Example/Program.cs | 36 +++++++ SendGrid/SendGrid/Resources/CustomFields.cs | 103 ++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 SendGrid/SendGrid/Resources/CustomFields.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 9b7c9733a..adafb4838 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -43,6 +43,7 @@ private static void Main() GlobalSuppressions(httpClient); GlobalStats(httpClient); Lists(httpClient); + CustomFields(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -269,5 +270,40 @@ private static void Lists(HttpClient httpClient) Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } + + private static void CustomFields(HttpClient httpClient) + { + Console.WriteLine("\n***** CUSTOM FIELDS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + var firstField = client.CustomFields.CreateAsync("first_field", FieldType.Text).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", firstField.Name, firstField.Id); + + var secondField = client.CustomFields.CreateAsync("second_field", FieldType.Number).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", secondField.Name, secondField.Id); + + var thirdField = client.CustomFields.CreateAsync("third field", FieldType.Date).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", thirdField.Name, thirdField.Id); + + var fields = client.CustomFields.GetAllAsync().Result; + Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); + + client.CustomFields.DeleteAsync(firstField.Id).Wait(); + Console.WriteLine("Field {0} deleted", firstField.Id); + + client.CustomFields.DeleteAsync(secondField.Id).Wait(); + Console.WriteLine("Field {0} deleted", secondField.Id); + + client.CustomFields.DeleteAsync(thirdField.Id).Wait(); + Console.WriteLine("Field {0} deleted", thirdField.Id); + + fields = client.CustomFields.GetAllAsync().Result; + Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); + + Console.WriteLine("\n\nPress any key to continue"); + Console.ReadKey(); + } } } diff --git a/SendGrid/SendGrid/Resources/CustomFields.cs b/SendGrid/SendGrid/Resources/CustomFields.cs new file mode 100644 index 000000000..da58575bd --- /dev/null +++ b/SendGrid/SendGrid/Resources/CustomFields.cs @@ -0,0 +1,103 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Threading.Tasks; + +namespace SendGrid.Resources +{ + public class CustomFields + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Recipients object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/contactdb.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public CustomFields(Client client, string endpoint = "v3/contactdb/custom_fields") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(string name, FieldType type) + { + var data = new JObject() + { + { "name", name }, + { "type", JToken.Parse(JsonConvert.SerializeObject(type, Formatting.None, new StringEnumConverter())).Value() } + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var field = JObject.Parse(responseContent).ToObject(); + return field; + } + + public async Task GetAllAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + //{ + // "custom_fields": [ + // { + // "id": 1, + // "name": "birthday", + // "type": "date" + // }, + // { + // "id": 2, + // "name": "middle_name", + // "type": "text" + // }, + // { + // "id": 3, + // "name": "favorite_number", + // "type": "number" + // } + // ] + //} + // We use a dynamic object to get rid of the 'custom_fields' property and simply return an array of custom fields + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.custom_fields; + + var fields = dynamicArray.ToObject(); + return fields; + } + + public async Task GetAsync(int fieldId) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, fieldId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var field = JObject.Parse(responseContent).ToObject(); + return field; + } + + public async Task DeleteAsync(int fieldId) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, fieldId)); + response.EnsureSuccess(); + } + + public async Task GetReservedFieldsAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var fields = JArray.Parse(responseContent).ToObject(); + return fields; + } + } +} \ No newline at end of file From be134ae69574416a39d39a2559e65e3efd485528 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 1 Feb 2016 12:46:13 -0600 Subject: [PATCH 14/52] Add an overloaded RequestAsync to accept JArray and also refactor it to be able to handle content with any HTTP verb (as opposed to only POST and PATCH). These two changes are necessary in order to handle calls such as 'Delete Multiple lists' for example which allows an array of integers to be in the body of the DELETE request. --- SendGrid/SendGrid/Client.cs | 80 ++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 6e86be196..76a1ecef8 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -1,12 +1,12 @@ -using System; +using Newtonsoft.Json.Linq; +using SendGrid.Resources; +using System; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; -using System.Threading.Tasks; using System.Text; -using SendGrid.Resources; -using System.Net; -using Newtonsoft.Json.Linq; +using System.Threading.Tasks; namespace SendGrid { @@ -51,6 +51,32 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/") /// An JObject representing the resource's data /// An asyncronous task private async Task RequestAsync(Methods method, string endpoint, JObject data) + { + var content = (data == null ? null : new StringContent(data.ToString(), Encoding.UTF8, MediaType)); + return await RequestAsync(method, endpoint, content); + } + + /// + /// Create a client that connects to the SendGrid Web API + /// + /// HTTP verb, case-insensitive + /// Resource endpoint, do not prepend slash + /// An JArray representing the resource's data + /// An asyncronous task + private async Task RequestAsync(Methods method, string endpoint, JArray data) + { + var content = (data == null ? null : new StringContent(data.ToString(), Encoding.UTF8, MediaType)); + return await RequestAsync(method, endpoint, content); + } + + /// + /// Create a client that connects to the SendGrid Web API + /// + /// HTTP verb, case-insensitive + /// Resource endpoint, do not prepend slash + /// A StringContent representing the content of the http request + /// An asyncronous task + private async Task RequestAsync(Methods method, string endpoint, StringContent content) { using (var client = new HttpClient()) { @@ -62,31 +88,30 @@ private async Task RequestAsync(Methods method, string endp client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "sendgrid/" + Version + ";csharp"); + var methodAsString = ""; switch (method) { - case Methods.GET: - return await client.GetAsync(endpoint); - case Methods.POST: - return await client.PostAsJsonAsync(endpoint, data); - case Methods.PATCH: - endpoint = _baseUri + endpoint; - StringContent content = new StringContent(data.ToString(), Encoding.UTF8, MediaType); - HttpRequestMessage request = new HttpRequestMessage - { - Method = new HttpMethod("PATCH"), - RequestUri = new Uri(endpoint), - Content = content - }; - return await client.SendAsync(request); - case Methods.DELETE: - return await client.DeleteAsync(endpoint); + case Methods.GET: methodAsString = "GET"; break; + case Methods.POST: methodAsString = "POST"; break; + case Methods.PATCH: methodAsString = "PATCH"; break; + case Methods.DELETE: methodAsString = "DELETE"; break; default: - HttpResponseMessage response = new HttpResponseMessage(); - response.StatusCode = HttpStatusCode.MethodNotAllowed; var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; - response.Content = new StringContent(message); + var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) + { + Content = new StringContent(message) + }; return response; } + + var postRequest = new HttpRequestMessage + { + Method = new HttpMethod(methodAsString), + RequestUri = new Uri(_baseUri + endpoint), + Content = content + }; + return await client.SendAsync(postRequest); + } catch (Exception ex) { @@ -104,7 +129,7 @@ private async Task RequestAsync(Methods method, string endp /// The resulting message from the API call public async Task Get(string endpoint) { - return await RequestAsync(Methods.GET, endpoint, null); + return await RequestAsync(Methods.GET, endpoint, (StringContent)null); } /// Resource endpoint, do not prepend slash @@ -116,10 +141,11 @@ public async Task Post(string endpoint, JObject data) } /// Resource endpoint, do not prepend slash + /// An optional JArray representing the resource's data /// The resulting message from the API call - public async Task Delete(string endpoint) + public async Task Delete(string endpoint, JArray data = null) { - return await RequestAsync(Methods.DELETE, endpoint, null); + return await RequestAsync(Methods.DELETE, endpoint, data); } /// Resource endpoint, do not prepend slash From 7dcfd55056e32e93a502df251b2a2a37ced5ebcd Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 1 Feb 2016 13:18:01 -0600 Subject: [PATCH 15/52] Reformat --- SendGrid/SendGrid/Client.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 76a1ecef8..709ea254f 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -104,7 +104,7 @@ private async Task RequestAsync(Methods method, string endp return response; } - var postRequest = new HttpRequestMessage + var postRequest = new HttpRequestMessage() { Method = new HttpMethod(methodAsString), RequestUri = new Uri(_baseUri + endpoint), @@ -115,11 +115,11 @@ private async Task RequestAsync(Methods method, string endp } catch (Exception ex) { - HttpResponseMessage response = new HttpResponseMessage(); - string message; - message = (ex is HttpRequestException) ? ".NET HttpRequestException" : ".NET Exception"; - message = message + ", raw message: \n\n"; - response.Content = new StringContent(message + ex.Message); + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent(message) + }; return response; } } From b9ffdeaa9d973ffdd9b14837ce2fc7ecf13eb354 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 2 Feb 2016 16:52:05 -0600 Subject: [PATCH 16/52] Strongly typed GlobalStats --- SendGrid/Example/Example.csproj | 3 ++ SendGrid/Example/Program.cs | 57 +++++++++------------- SendGrid/SendGrid/Model/AggregateBy.cs | 16 ++++++ SendGrid/SendGrid/Model/GlobalStat.cs | 14 ++++++ SendGrid/SendGrid/Model/Metrics.cs | 55 +++++++++++++++++++++ SendGrid/SendGrid/Model/Stats.cs | 16 ++++++ SendGrid/SendGrid/Resources/GlobalStats.cs | 33 +++++++------ SendGrid/SendGrid/SendGrid.csproj | 5 ++ SendGrid/SendGrid/Utilities/Extensions.cs | 31 ++++++++++++ 9 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 SendGrid/SendGrid/Model/AggregateBy.cs create mode 100644 SendGrid/SendGrid/Model/GlobalStat.cs create mode 100644 SendGrid/SendGrid/Model/Metrics.cs create mode 100644 SendGrid/SendGrid/Model/Stats.cs create mode 100644 SendGrid/SendGrid/Utilities/Extensions.cs diff --git a/SendGrid/Example/Example.csproj b/SendGrid/Example/Example.csproj index 786d6fb25..77b7931b1 100644 --- a/SendGrid/Example/Example.csproj +++ b/SendGrid/Example/Example.csproj @@ -44,6 +44,9 @@ + + Example.Program + diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 447bd0478..a2bbebeae 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -1,8 +1,9 @@ -using System; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Mail; -using Newtonsoft.Json.Linq; namespace Example { @@ -22,7 +23,7 @@ private static void Main() GlobalSuppressions(); GlobalStats(); } - + private static void SendAsync(SendGrid.SendGridMessage message) { string apikey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY"); @@ -141,7 +142,7 @@ private static void UnsubscribeGroups() Console.WriteLine("Unsubscribe Group Deleted.\n\nPress any key to end."); Console.ReadKey(); } - + private static void Suppressions() { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); @@ -219,45 +220,31 @@ private static void GlobalSuppressions() private static void GlobalStats() { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + Console.WriteLine("\n***** GLOBAL STATS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey); // Global Stats provide all of your user’s email statistics for a given date range. - var startDate = "2015-11-01"; - HttpResponseMessage response = client.GlobalStats.Get(startDate).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and no end date.\n\nPress any key to continue."); - Console.ReadKey(); + var startDate = new DateTime(2015, 11, 01); + var stats = client.GlobalStats.GetAsync(startDate).Result; + Console.WriteLine("Number of stats with start date {0} and no end date: {1}", startDate.ToShortDateString(), stats.Length); - var endDate = "2015-12-01"; - response = client.GlobalStats.Get(startDate, endDate).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + ".\n\nPress any key to continue."); - Console.ReadKey(); + var endDate = new DateTime(2015, 12, 01); + stats = client.GlobalStats.GetAsync(startDate, endDate).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1}: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - var aggregatedBy = "day"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); - Console.ReadKey(); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Day).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by day: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - aggregatedBy = "week"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); - Console.ReadKey(); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Week).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by week: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); - aggregatedBy = "month"; - response = client.GlobalStats.Get(startDate, endDate, aggregatedBy).Result; - Console.WriteLine(response.StatusCode); - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - Console.WriteLine("Display global email stats, with start date " + startDate + "and end date " + endDate + " and aggregated by " + aggregatedBy + ".\n\nPress any key to continue."); + stats = client.GlobalStats.GetAsync(startDate, endDate, AggregateBy.Month).Result; + Console.WriteLine("Number of stats with start date {0} and end date {1} and aggregated by month: {2}", startDate.ToShortDateString(), endDate.ToShortDateString(), stats.Length); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } - } } diff --git a/SendGrid/SendGrid/Model/AggregateBy.cs b/SendGrid/SendGrid/Model/AggregateBy.cs new file mode 100644 index 000000000..153b5f80b --- /dev/null +++ b/SendGrid/SendGrid/Model/AggregateBy.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace SendGrid.Model +{ + public enum AggregateBy + { + [Description("none")] + None, + [Description("day")] + Day, + [Description("week")] + Week, + [Description("month")] + Month + } +} diff --git a/SendGrid/SendGrid/Model/GlobalStat.cs b/SendGrid/SendGrid/Model/GlobalStat.cs new file mode 100644 index 000000000..8e2d49130 --- /dev/null +++ b/SendGrid/SendGrid/Model/GlobalStat.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; + +namespace SendGrid.Model +{ + public class GlobalStat + { + [JsonProperty("date")] + public DateTime Date { get; set; } + + [JsonProperty("stats")] + public Stat[] Stats { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Metrics.cs b/SendGrid/SendGrid/Model/Metrics.cs new file mode 100644 index 000000000..c25993532 --- /dev/null +++ b/SendGrid/SendGrid/Model/Metrics.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Metrics + { + [JsonProperty("blocks")] + public long Blocks { get; set; } + + [JsonProperty("bounce_drops")] + public long BounceDrops { get; set; } + + [JsonProperty("bounces")] + public long Bounces { get; set; } + + [JsonProperty("clicks")] + public long Clicks { get; set; } + + [JsonProperty("deferred")] + public long Deferred { get; set; } + + [JsonProperty("delivered")] + public long Delivered { get; set; } + + [JsonProperty("invalid_emails")] + public long InvalidEmails { get; set; } + + [JsonProperty("opens")] + public long Opens { get; set; } + + [JsonProperty("processed")] + public long Processed { get; set; } + + [JsonProperty("requests")] + public long Requests { get; set; } + + [JsonProperty("spam_report_drops")] + public long SpamReportDrops { get; set; } + + [JsonProperty("spam_reports")] + public long SpamReports { get; set; } + + [JsonProperty("unique_clicks")] + public long UniqueClicks { get; set; } + + [JsonProperty("unique_opens")] + public long UniqueOpens { get; set; } + + [JsonProperty("unsubscribe_drops")] + public long UnsubscribeDrops { get; set; } + + [JsonProperty("unsubscribes")] + public long Unsubscribes { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Stats.cs b/SendGrid/SendGrid/Model/Stats.cs new file mode 100644 index 000000000..3e32b7fe0 --- /dev/null +++ b/SendGrid/SendGrid/Model/Stats.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Stat + { + [JsonProperty("metrics")] + public Metrics Metrics { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/GlobalStats.cs b/SendGrid/SendGrid/Resources/GlobalStats.cs index 73f0b336b..590c36004 100644 --- a/SendGrid/SendGrid/Resources/GlobalStats.cs +++ b/SendGrid/SendGrid/Resources/GlobalStats.cs @@ -1,5 +1,7 @@ -using System; -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System; using System.Threading.Tasks; using System.Web; @@ -25,24 +27,23 @@ public GlobalStats(Client client, string endpoint = "v3/stats") /// /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/global.html /// - /// The starting date of the statistics to retrieve, formatted as YYYY-MM-DD. - /// The end date of the statistics to retrieve, formatted as YYYY-MM-DD. Defaults to today. + /// The starting date of the statistics to retrieve. + /// The end date of the statistics to retrieve. Defaults to today. /// How to group the statistics, must be day|week|month /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/global.html - public async Task Get(string startDate, string endDate = null, string aggregatedBy = null) + public async Task GetAsync(DateTime startDate, DateTime? endDate = null, AggregateBy aggregatedBy = AggregateBy.None) { var query = HttpUtility.ParseQueryString(string.Empty); - query["start_date"] = startDate; - if (endDate != null) - { - query["end_date"] = endDate; - } - if (aggregatedBy != null) - { - query["aggregated_by"] = aggregatedBy; - } - return await _client.Get(_endpoint + "?" + query); - } + query["start_date"] = startDate.ToString("yyyy-MM-dd"); + if (endDate.HasValue) query["end_date"] = endDate.Value.ToString("yyyy-MM-dd"); + if (aggregatedBy != AggregateBy.None) query["aggregated_by"] = aggregatedBy.GetDescription(); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + var responseContent = await response.Content.ReadAsStringAsync(); + var getStatsResult = JArray.Parse(responseContent).ToObject(); + return getStatsResult; + } } } \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index 2f5bcf68f..c76657cf3 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,12 +64,17 @@ + + + + + diff --git a/SendGrid/SendGrid/Utilities/Extensions.cs b/SendGrid/SendGrid/Utilities/Extensions.cs new file mode 100644 index 000000000..952a25675 --- /dev/null +++ b/SendGrid/SendGrid/Utilities/Extensions.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using System.Net.Http; + +namespace SendGrid.Utilities +{ + public static class Extensions + { + public static string GetDescription(this Enum value) + { + var fieldInfo = value.GetType().GetField(value.ToString()); + if (fieldInfo == null) return value.ToString(); + + var attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attributes == null || attributes.Length == 0) return value.ToString(); + + var descriptionAttribute = attributes[0] as DescriptionAttribute; + return (descriptionAttribute == null ? value.ToString() : descriptionAttribute.Description); + } + + public static void EnsureSuccess(this HttpResponseMessage response) + { + if (response.IsSuccessStatusCode) return; + + var content = response.Content.ReadAsStringAsync().Result; + if (response.Content != null) response.Content.Dispose(); + + throw new Exception(content); + } + } +} From 017895a8f545a416c60de5eb86129231d1738f57 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 3 Feb 2016 11:52:52 -0600 Subject: [PATCH 17/52] Strongly typed GlobalSuppressions --- SendGrid/Example/Program.cs | 46 +++++++------------ .../SendGrid/Resources/GlobalSuppressions.cs | 38 +++++++++++---- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index a2bbebeae..c967af094 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -181,40 +181,28 @@ private static void Suppressions() private static void GlobalSuppressions() { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); - // CHECK IF EMAIL IS ON THE GLOBAL SUPPRESSION LIST - var email = "elmer.thomas+test_global@gmail.com"; - HttpResponseMessage responseGetUnique = client.GlobalSuppressions.Get(email).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("Determines if the given email is listed on the Global Suppressions list. Press any key to continue."); - Console.ReadKey(); + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey); // ADD EMAILS TO THE GLOBAL SUPPRESSION LIST - string[] emails = { "example@example.com", "example2@example.com" }; - HttpResponseMessage responsePost = client.GlobalSuppressions.Post(emails).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails added to Global Suppression Group.\n\nPress any key to continue."); - Console.ReadKey(); + var emails = new[] { "example@example.com", "example2@example.com" }; + client.GlobalSuppressions.AddAsync(emails).Wait(); + Console.WriteLine("\nThe following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); + Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); + Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); // DELETE EMAILS FROM THE GLOBAL SUPPRESSION GROUP - Console.WriteLine("Deleting emails from Global Suppression Group, please wait."); - HttpResponseMessage responseDelete1 = client.GlobalSuppressions.Delete("example@example.com").Result; - Console.WriteLine(responseDelete1.StatusCode); - HttpResponseMessage responseDelete2 = client.GlobalSuppressions.Delete("example2@example.com").Result; - Console.WriteLine(responseDelete2.StatusCode); - HttpResponseMessage responseFinal = client.GlobalSuppressions.Get("example@example.com").Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - HttpResponseMessage responseFinal2 = client.GlobalSuppressions.Get("example2@example.com").Result; - Console.WriteLine(responseFinal2.StatusCode); - Console.WriteLine(responseFinal2.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails removed from Global Suppression Group.\n\nPress any key to end."); + client.GlobalSuppressions.RemoveAsync(emails[0]).Wait(); + Console.WriteLine("{0} has been removed from the global suppression list", emails[0]); + client.GlobalSuppressions.RemoveAsync(emails[1]).Wait(); + Console.WriteLine("{0} has been removed from the global suppression list", emails[1]); + + Console.WriteLine("Is {0} unsubscribed (should be false): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); + Console.WriteLine("Is {0} unsubscribed (should be false): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } diff --git a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs index 36fcd1852..a96723792 100644 --- a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs +++ b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs @@ -1,6 +1,8 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -26,9 +28,24 @@ public GlobalSuppressions(Client client, string endpoint = "v3/asm/suppressions/ /// /// email address to check /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Get(string email) + public async Task IsUnsubscribedAsync(string email) { - return await _client.Get(_endpoint + "/" + email); + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + + // If the email address is on the global suppression list, the response will look like this: + // { + // "recipient_email": "{email}" + // } + // If the email address is not on the global suppression list, the response will be empty + // + // Therefore, we check for the presence of the 'recipient_email' to indicate if the email + // address is on the global suppression list or not. + + var responseContent = await response.Content.ReadAsStringAsync(); + var dynamicObject = JObject.Parse(responseContent); + var propertyDictionary = (IDictionary)dynamicObject; + return propertyDictionary.ContainsKey("recipient_email"); } /// @@ -36,12 +53,12 @@ public async Task Get(string email) /// /// Array of email addresses to add to the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Post(string[] emails) + public async Task AddAsync(IEnumerable emails) { - JArray receipient_emails = new JArray(); - foreach (string email in emails) { receipient_emails.Add(email); } + var receipient_emails = new JArray(emails.ToArray()); var data = new JObject(new JProperty("recipient_emails", receipient_emails)); - return await _client.Post(_endpoint, data); + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); } /// @@ -49,9 +66,10 @@ public async Task Post(string[] emails) /// /// email address to be removed from the global suppressions group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html - public async Task Delete(string email) + public async Task RemoveAsync(string email) { - return await _client.Delete(_endpoint + "/" + email); + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); } } } \ No newline at end of file From 356dc2fae5ad5e945c63d43675ded3e13674991e Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 3 Feb 2016 15:50:17 -0600 Subject: [PATCH 18/52] Strongly typed UnsubscribeGroups Also allow the HttpClient to be injected (this is useful for mocking and for using Fiddler) --- SendGrid/Example/Program.cs | 99 ++++++++++--------- SendGrid/SendGrid/Client.cs | 94 +++++++++--------- SendGrid/SendGrid/Model/SuppressionGroup.cs | 19 ++++ .../SendGrid/Resources/GlobalSuppressions.cs | 3 +- .../SendGrid/Resources/UnsubscribeGroups.cs | 76 ++++++++++---- SendGrid/SendGrid/SendGrid.csproj | 1 + 6 files changed, 180 insertions(+), 112 deletions(-) create mode 100644 SendGrid/SendGrid/Model/SuppressionGroup.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index c967af094..6983c436e 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -2,8 +2,10 @@ using SendGrid.Model; using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Net.Mail; +using System.Net.Security; namespace Example { @@ -11,17 +13,36 @@ internal class Program { private static void Main() { + // Do you want to proxy requests through fiddler? + var useFiddler = false; + + if (useFiddler) + { + // This is necessary to ensure HTTPS traffic can be proxied through Fiddler without any certificate validation error. + // This is fine for testing but not advisable in production + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); + } + + // Direct all traffic through fiddler running on the local machine + var httpClient = new HttpClient( + new HttpClientHandler() + { + Proxy = new WebProxy("http://localhost:8888"), + UseProxy = useFiddler + } + ); + // Test sending email var to = "example@example.com"; var from = "example@example.com"; var fromName = "Jane Doe"; SendEmail(to, from, fromName); // Test viewing, creating, modifying and deleting API keys through our v3 Web API - ApiKeys(); - UnsubscribeGroups(); - Suppressions(); - GlobalSuppressions(); - GlobalStats(); + ApiKeys(httpClient); + UnsubscribeGroups(httpClient); + Suppressions(httpClient); + GlobalSuppressions(httpClient); + GlobalStats(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -62,10 +83,10 @@ private static void SendEmail(string to, string from, string fromName) SendAsync(myMessage); } - private static void ApiKeys() + private static void ApiKeys(HttpClient httpClient) { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // GET API KEYS HttpResponseMessage responseGet = client.ApiKeys.Get().Result; @@ -102,48 +123,38 @@ private static void ApiKeys() Console.ReadKey(); } - private static void UnsubscribeGroups() + private static void UnsubscribeGroups(HttpClient httpClient) { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + Console.WriteLine("\n***** UNSUBSCRIBE GROUPS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + // CREATE A NEW SUPPRESSION GROUP + var newGroup = client.UnsubscribeGroups.CreateAsync("New group", "This is a new group for testing purposes", false).Result; + Console.WriteLine("Unique ID of the new suppresion group: {0}", newGroup.Id); + + // UPDATE A SUPPRESSION GROUP + var updatedGroup = client.UnsubscribeGroups.UpdateAsync(newGroup.Id, "This is the updated name").Result; + Console.WriteLine("Suppresion group {0} updated", updatedGroup.Id); // GET UNSUBSCRIBE GROUPS - HttpResponseMessage responseGet = client.UnsubscribeGroups.Get().Result; - Console.WriteLine(responseGet.StatusCode); - Console.WriteLine(responseGet.Content.ReadAsStringAsync().Result); - Console.WriteLine("These are your current Unsubscribe Groups. Press any key to continue."); - Console.ReadKey(); + var groups = client.UnsubscribeGroups.GetAllAsync().Result; + Console.WriteLine("There are {0} suppresion groups", groups.Length); // GET A PARTICULAR UNSUBSCRIBE GROUP - int unsubscribeGroupID = 69; - HttpResponseMessage responseGetUnique = client.UnsubscribeGroups.Get(unsubscribeGroupID).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("This is an Unsubscribe Group with ID: " + unsubscribeGroupID.ToString() + ".\n\nPress any key to continue."); - Console.ReadKey(); - - // POST UNSUBSCRIBE GROUP - HttpResponseMessage responsePost = client.UnsubscribeGroups.Post("C Sharp Unsubscribes", "Testing the C Sharp Library", false).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - var unsubscribeGroupId = jsonObject.id.ToString(); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Unsubscribe Group created.\n\nPress any key to continue."); - Console.ReadKey(); + var group = client.UnsubscribeGroups.GetAsync(newGroup.Id).Result; + Console.WriteLine("Retrieved unsubscribe group {0}: {1}", group.Id, group.Name); // DELETE UNSUBSCRIBE GROUP - Console.WriteLine("Deleting Unsubscribe Group, please wait."); - HttpResponseMessage responseDelete = client.UnsubscribeGroups.Delete(unsubscribeGroupId).Result; - Console.WriteLine(responseDelete.StatusCode); - HttpResponseMessage responseFinal = client.UnsubscribeGroups.Get().Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - Console.WriteLine("Unsubscribe Group Deleted.\n\nPress any key to end."); + client.UnsubscribeGroups.DeleteAsync(newGroup.Id).Wait(); + Console.WriteLine("Suppression group {0} deleted", newGroup.Id); + + Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } - private static void Suppressions() + private static void Suppressions(HttpClient httpClient) { String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey); @@ -179,17 +190,17 @@ private static void Suppressions() Console.ReadKey(); } - private static void GlobalSuppressions() + private static void GlobalSuppressions(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // ADD EMAILS TO THE GLOBAL SUPPRESSION LIST var emails = new[] { "example@example.com", "example2@example.com" }; client.GlobalSuppressions.AddAsync(emails).Wait(); - Console.WriteLine("\nThe following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); + Console.WriteLine("The following emails have been added to the global suppression list: {0}", string.Join(", ", emails)); Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[0], client.GlobalSuppressions.IsUnsubscribedAsync(emails[0]).Result); Console.WriteLine("Is {0} unsubscribed (should be true): {1}", emails[1], client.GlobalSuppressions.IsUnsubscribedAsync(emails[1]).Result); @@ -206,12 +217,12 @@ private static void GlobalSuppressions() Console.ReadKey(); } - private static void GlobalStats() + private static void GlobalStats(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL STATS *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); // Global Stats provide all of your user’s email statistics for a given date range. var startDate = new DateTime(2015, 11, 01); diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 709ea254f..968f4687a 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -13,34 +13,44 @@ namespace SendGrid public class Client { private string _apiKey; - public APIKeys ApiKeys; - public UnsubscribeGroups UnsubscribeGroups; - public Suppressions Suppressions; - public GlobalSuppressions GlobalSuppressions; - public GlobalStats GlobalStats; - public string Version; private Uri _baseUri; + private HttpClient _httpClient; private const string MediaType = "application/json"; private enum Methods { GET, POST, PATCH, DELETE } + public APIKeys ApiKeys { get; private set; } + public UnsubscribeGroups UnsubscribeGroups { get; private set; } + public Suppressions Suppressions { get; private set; } + public GlobalSuppressions GlobalSuppressions { get; private set; } + public GlobalStats GlobalStats { get; private set; } + public string Version { get; private set; } + /// /// Create a client that connects to the SendGrid Web API /// /// Your SendGrid API Key /// Base SendGrid API Uri - public Client(string apiKey, string baseUri = "https://api.sendgrid.com/") + public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpClient httpClient = null) { _baseUri = new Uri(baseUri); _apiKey = apiKey; + Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); ApiKeys = new APIKeys(this); UnsubscribeGroups = new UnsubscribeGroups(this); Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + + _httpClient = httpClient ?? new HttpClient(); + _httpClient.BaseAddress = _baseUri; + _httpClient.DefaultRequestHeaders.Accept.Clear(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaType)); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", string.Format("sendgrid/{0};csharp", Version)); } /// @@ -78,50 +88,40 @@ private async Task RequestAsync(Methods method, string endp /// An asyncronous task private async Task RequestAsync(Methods method, string endpoint, StringContent content) { - using (var client = new HttpClient()) + try { - try + var methodAsString = ""; + switch (method) { - client.BaseAddress = _baseUri; - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaType)); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "sendgrid/" + Version + ";csharp"); - - var methodAsString = ""; - switch (method) - { - case Methods.GET: methodAsString = "GET"; break; - case Methods.POST: methodAsString = "POST"; break; - case Methods.PATCH: methodAsString = "PATCH"; break; - case Methods.DELETE: methodAsString = "DELETE"; break; - default: - var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; - var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) - { - Content = new StringContent(message) - }; - return response; - } - - var postRequest = new HttpRequestMessage() - { - Method = new HttpMethod(methodAsString), - RequestUri = new Uri(_baseUri + endpoint), - Content = content - }; - return await client.SendAsync(postRequest); - + case Methods.GET: methodAsString = "GET"; break; + case Methods.POST: methodAsString = "POST"; break; + case Methods.PATCH: methodAsString = "PATCH"; break; + case Methods.DELETE: methodAsString = "DELETE"; break; + default: + var message = "{\"errors\":[{\"message\":\"Bad method call, supported methods are GET, POST, PATCH and DELETE\"}]}"; + var response = new HttpResponseMessage(HttpStatusCode.MethodNotAllowed) + { + Content = new StringContent(message) + }; + return response; } - catch (Exception ex) + + var httpRequest = new HttpRequestMessage() { - var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); - var response = new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent(message) - }; - return response; - } + Method = new HttpMethod(methodAsString), + RequestUri = new Uri(_baseUri + endpoint), + Content = content + }; + return await _httpClient.SendAsync(httpRequest); + } + catch (Exception ex) + { + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent(message) + }; + return response; } } diff --git a/SendGrid/SendGrid/Model/SuppressionGroup.cs b/SendGrid/SendGrid/Model/SuppressionGroup.cs new file mode 100644 index 000000000..26e3b72a1 --- /dev/null +++ b/SendGrid/SendGrid/Model/SuppressionGroup.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class SuppressionGroup + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("is_default")] + public bool IsDefault { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs index a96723792..c31cd716f 100644 --- a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs +++ b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs @@ -55,8 +55,7 @@ public async Task IsUnsubscribedAsync(string email) /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html public async Task AddAsync(IEnumerable emails) { - var receipient_emails = new JArray(emails.ToArray()); - var data = new JObject(new JProperty("recipient_emails", receipient_emails)); + var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); var response = await _client.Post(_endpoint, data); response.EnsureSuccess(); } diff --git a/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs b/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs index db8e4d228..51b55f528 100644 --- a/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs +++ b/SendGrid/SendGrid/Resources/UnsubscribeGroups.cs @@ -1,6 +1,7 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -25,36 +26,72 @@ public UnsubscribeGroups(Client client, string endpoint = "v3/asm/groups") /// Retrieve all suppression groups associated with the user. /// /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Get() + public async Task GetAllAsync() { - return await _client.Get(_endpoint); + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var groups = JArray.Parse(responseContent).ToObject(); + return groups; } /// /// Get information on a single suppression group. /// - /// ID of the suppression group to delete + /// ID of the suppression group to delete /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Get(int unsubscribeGroupId) + public async Task GetAsync(int groupId) { - return await _client.Get(_endpoint + "/" + unsubscribeGroupId); + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, groupId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; } /// /// Create a new suppression group. /// - /// The name of the new suppression group - /// A description of the suppression group - /// Default value is false + /// The name of the new suppression group + /// A description of the suppression group + /// Default value is false + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html + public async Task CreateAsync(string name, string description, bool isDefault) + { + var data = new JObject() + { + { "name", name }, + { "description", description }, + { "is_default", isDefault } + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; + } + + /// + /// Update an existing suppression group. + /// + /// The name of the new suppression group + /// A description of the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Post(string unsubscribeGroupName, - string unsubscribeGroupDescription, - bool unsubscribeGroupIsDefault) + public async Task UpdateAsync(int groupId, string name = null, string description = null) { - var data = new JObject {{"name", unsubscribeGroupName}, - {"description", unsubscribeGroupDescription}, - {"is_default", unsubscribeGroupIsDefault}}; - return await _client.Post(_endpoint, data); + var data = new JObject(); + if (name != null) data.Add("name", name); + if (description != null) data.Add("description", description); + + var response = await _client.Patch(string.Format("{0}/{1}", _endpoint, groupId), data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var group = JObject.Parse(responseContent).ToObject(); + return group; } /// @@ -62,9 +99,10 @@ public async Task Get(int unsubscribeGroupId) /// /// ID of the suppression group to delete /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/groups.html - public async Task Delete(string unsubscribeGroupId) + public async Task DeleteAsync(int groupId) { - return await _client.Delete(_endpoint + "/" + unsubscribeGroupId); + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, groupId)); + response.EnsureSuccess(); } } } \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index c76657cf3..f0a37c1be 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,6 +64,7 @@ + From 3a22e631ac8ab0a1400d290532436e32af28c5ae Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Thu, 4 Feb 2016 12:30:13 -0600 Subject: [PATCH 19/52] Strongly typed 'Suppression' --- SendGrid/Example/Program.cs | 75 +++++++++------------ SendGrid/SendGrid/Resources/Suppressions.cs | 47 +++++++++---- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 6983c436e..7fee1e530 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -31,7 +31,7 @@ private static void Main() UseProxy = useFiddler } ); - + // Test sending email var to = "example@example.com"; var from = "example@example.com"; @@ -40,7 +40,6 @@ private static void Main() // Test viewing, creating, modifying and deleting API keys through our v3 Web API ApiKeys(httpClient); UnsubscribeGroups(httpClient); - Suppressions(httpClient); GlobalSuppressions(httpClient); GlobalStats(httpClient); } @@ -132,20 +131,48 @@ private static void UnsubscribeGroups(HttpClient httpClient) // CREATE A NEW SUPPRESSION GROUP var newGroup = client.UnsubscribeGroups.CreateAsync("New group", "This is a new group for testing purposes", false).Result; - Console.WriteLine("Unique ID of the new suppresion group: {0}", newGroup.Id); - + Console.WriteLine("Unique ID of the new unsubscribe group: {0}", newGroup.Id); + // UPDATE A SUPPRESSION GROUP var updatedGroup = client.UnsubscribeGroups.UpdateAsync(newGroup.Id, "This is the updated name").Result; - Console.WriteLine("Suppresion group {0} updated", updatedGroup.Id); + Console.WriteLine("Unsubscribe group {0} updated", updatedGroup.Id); // GET UNSUBSCRIBE GROUPS var groups = client.UnsubscribeGroups.GetAllAsync().Result; - Console.WriteLine("There are {0} suppresion groups", groups.Length); + Console.WriteLine("There are {0} unsubscribe groups", groups.Length); // GET A PARTICULAR UNSUBSCRIBE GROUP var group = client.UnsubscribeGroups.GetAsync(newGroup.Id).Result; Console.WriteLine("Retrieved unsubscribe group {0}: {1}", group.Id, group.Name); + // ADD A FEW ADDRESSES TO UNSUBSCRIBE GROUP + client.Suppressions.AddAddressToUnsubscribeGroupAsync(group.Id, "test1@example.com").Wait(); + Console.WriteLine("Added test1@example.com to unsubscribe group {0}", group.Id); + client.Suppressions.AddAddressToUnsubscribeGroupAsync(group.Id, "test2@example.com").Wait(); + Console.WriteLine("Added test2@example.com to unsubscribe group {0}", group.Id); + + // GET THE ADDRESSES IN A GROUP + var unsubscribedAddresses = client.Suppressions.GetUnsubscribedAddressesAsync(group.Id).Result; + Console.WriteLine("There are {0} unsubscribed addresses in group {1}", unsubscribedAddresses.Length, group.Id); + + // REMOVE ALL ADDRESSES FROM UNSUBSCRIBE GROUP + foreach (var address in unsubscribedAddresses) + { + client.Suppressions.RemoveAddressFromSuppressionGroupAsync(group.Id, address).Wait(); + Console.WriteLine("{0} removed from unsubscribe group {1}", address, group.Id); + } + + // MAKE SURE THERE ARE NO ADDRESSES IN THE GROUP + unsubscribedAddresses = client.Suppressions.GetUnsubscribedAddressesAsync(group.Id).Result; + if (unsubscribedAddresses.Length == 0) + { + Console.WriteLine("As expected, there are no more addresses in group {0}", group.Id); + } + else + { + Console.WriteLine("We expected the group {1} to be empty but instead we found {0} unsubscribed addresses.", unsubscribedAddresses.Length, group.Id); + } + // DELETE UNSUBSCRIBE GROUP client.UnsubscribeGroups.DeleteAsync(newGroup.Id).Wait(); Console.WriteLine("Suppression group {0} deleted", newGroup.Id); @@ -154,42 +181,6 @@ private static void UnsubscribeGroups(HttpClient httpClient) Console.ReadKey(); } - private static void Suppressions(HttpClient httpClient) - { - String apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); - var client = new SendGrid.Client(apiKey); - - // GET SUPPRESSED ADDRESSES FOR A GIVEN GROUP - int groupID = 69; - HttpResponseMessage responseGetUnique = client.Suppressions.Get(groupID).Result; - Console.WriteLine(responseGetUnique.StatusCode); - Console.WriteLine(responseGetUnique.Content.ReadAsStringAsync().Result); - Console.WriteLine("These are the suppressed emails with group ID: " + groupID.ToString() + ". Press any key to continue."); - Console.ReadKey(); - - // ADD EMAILS TO A SUPPRESSION GROUP - string[] emails = { "example@example.com", "example2@example.com" }; - HttpResponseMessage responsePost = client.Suppressions.Post(groupID, emails).Result; - var rawString = responsePost.Content.ReadAsStringAsync().Result; - dynamic jsonObject = JObject.Parse(rawString); - Console.WriteLine(responsePost.StatusCode); - Console.WriteLine(responsePost.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails added to Suppression Group:" + groupID.ToString() + ".\n\nPress any key to continue."); - Console.ReadKey(); - - // DELETE EMAILS FROM A SUPPRESSION GROUP - Console.WriteLine("Deleting emails from Suppression Group, please wait."); - HttpResponseMessage responseDelete1 = client.Suppressions.Delete(groupID, "example@example.com").Result; - Console.WriteLine(responseDelete1.StatusCode); - HttpResponseMessage responseDelete2 = client.Suppressions.Delete(groupID, "example2@example.com").Result; - Console.WriteLine(responseDelete2.StatusCode); - HttpResponseMessage responseFinal = client.Suppressions.Get(groupID).Result; - Console.WriteLine(responseFinal.StatusCode); - Console.WriteLine(responseFinal.Content.ReadAsStringAsync().Result); - Console.WriteLine("Emails removed from Suppression Group" + groupID.ToString() + "Deleted.\n\nPress any key to end."); - Console.ReadKey(); - } - private static void GlobalSuppressions(HttpClient httpClient) { Console.WriteLine("\n***** GLOBAL SUPPRESSION *****"); diff --git a/SendGrid/SendGrid/Resources/Suppressions.cs b/SendGrid/SendGrid/Resources/Suppressions.cs index 91bb38d60..523b52092 100644 --- a/SendGrid/SendGrid/Resources/Suppressions.cs +++ b/SendGrid/SendGrid/Resources/Suppressions.cs @@ -1,6 +1,8 @@ -using System.Net.Http; +using Newtonsoft.Json.Linq; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace SendGrid.Resources { @@ -26,9 +28,27 @@ public Suppressions(Client client, string endpoint = "v3/asm/groups") /// /// ID of the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Get(int groupId) + public async Task GetUnsubscribedAddressesAsync(int groupId) { - return await _client.Get(_endpoint + "/" + groupId.ToString() + "/suppressions"); + var response = await _client.Get(string.Format("{0}/{1}/suppressions", _endpoint, groupId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var suppressedAddresses = JArray.Parse(responseContent).ToObject(); + return suppressedAddresses; + } + + /// + /// Add recipient address to the suppressions list for a given group. + /// + /// If the group has been deleted, this request will add the address to the global suppression. + /// + /// ID of the suppression group + /// Email address to add to the suppression group + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html + public async Task AddAddressToUnsubscribeGroupAsync(int groupId, string email) + { + await AddAddressToUnsubscribeGroupAsync(groupId, new[] { email }); } /// @@ -37,24 +57,25 @@ public async Task Get(int groupId) /// If the group has been deleted, this request will add the address to the global suppression. /// /// ID of the suppression group - /// Array of email addresses to add to the suppression group + /// Email addresses to add to the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Post(int groupId, string[] emails) + public async Task AddAddressToUnsubscribeGroupAsync(int groupId, IEnumerable emails) { - JArray receipient_emails = new JArray(); - foreach (string email in emails) { receipient_emails.Add(email); } - var data = new JObject(new JProperty("recipient_emails", receipient_emails)); - return await _client.Post(_endpoint + "/" + groupId.ToString() + "/suppressions", data); + var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); + var response = await _client.Post(string.Format("{0}/{1}/suppressions", _endpoint, groupId), data); + response.EnsureSuccess(); } /// - /// Delete a suppression group. + /// Delete a recipient email from the suppressions list for a group. /// /// ID of the suppression group to delete + /// Email address to remove from the suppression group /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html - public async Task Delete(int groupId, string email) + public async Task RemoveAddressFromSuppressionGroupAsync(int groupId, string email) { - return await _client.Delete(_endpoint + "/" + groupId.ToString() + "/suppressions/" + email); + var response = await _client.Delete(string.Format("{0}/{1}/suppressions/{2}", _endpoint, groupId, email)); + response.EnsureSuccess(); } } } \ No newline at end of file From 3310daa931722cc3f38dc0977fe1ba83448aaf17 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Fri, 5 Feb 2016 11:16:26 -0600 Subject: [PATCH 20/52] Add overload Client.Post method to accept a JArray --- SendGrid/SendGrid/Client.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 968f4687a..e4e4dfe7b 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -140,6 +140,14 @@ public async Task Post(string endpoint, JObject data) return await RequestAsync(Methods.POST, endpoint, data); } + /// Resource endpoint, do not prepend slash + /// An JArray representing the resource's data + /// The resulting message from the API call + public async Task Post(string endpoint, JArray data) + { + return await RequestAsync(Methods.POST, endpoint, data); + } + /// Resource endpoint, do not prepend slash /// An optional JArray representing the resource's data /// The resulting message from the API call From b15c283d0a0364e10e41ec70d9c0a906d64ad2ed Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 14:39:29 -0600 Subject: [PATCH 21/52] Strongly typed Bounces Added overloaded Client.Delete methods to accept JObject in addition to JArray Added EpochConverter to convert Unix time to .NET DateTime when parsing JSON Added convenient extension methods to convert unix time to .NET DateTime --- SendGrid/SendGrid/Client.cs | 17 ++++ SendGrid/SendGrid/Model/Bounce.cs | 22 +++++ SendGrid/SendGrid/Resources/Bounces.cs | 91 +++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 3 + SendGrid/SendGrid/Utilities/EpochConverter.cs | 30 ++++++ SendGrid/SendGrid/Utilities/Extensions.cs | 12 +++ 6 files changed, 175 insertions(+) create mode 100644 SendGrid/SendGrid/Model/Bounce.cs create mode 100644 SendGrid/SendGrid/Resources/Bounces.cs create mode 100644 SendGrid/SendGrid/Utilities/EpochConverter.cs diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index e4e4dfe7b..995440486 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -26,6 +26,7 @@ private enum Methods public Suppressions Suppressions { get; private set; } public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } + public Bounces Bounces { get; private set; } public string Version { get; private set; } /// @@ -44,6 +45,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + Bounces = new Bounces(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; @@ -148,6 +150,21 @@ public async Task Post(string endpoint, JArray data) return await RequestAsync(Methods.POST, endpoint, data); } + /// Resource endpoint, do not prepend slash + /// The resulting message from the API call + public async Task Delete(string endpoint) + { + return await RequestAsync(Methods.DELETE, endpoint, (StringContent)null); + } + + /// Resource endpoint, do not prepend slash + /// An optional JObject representing the resource's data + /// The resulting message from the API call + public async Task Delete(string endpoint, JObject data = null) + { + return await RequestAsync(Methods.DELETE, endpoint, data); + } + /// Resource endpoint, do not prepend slash /// An optional JArray representing the resource's data /// The resulting message from the API call diff --git a/SendGrid/SendGrid/Model/Bounce.cs b/SendGrid/SendGrid/Model/Bounce.cs new file mode 100644 index 000000000..58c5cc884 --- /dev/null +++ b/SendGrid/SendGrid/Model/Bounce.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using SendGrid.Utilities; +using System; + +namespace SendGrid.Model +{ + public class Bounce + { + [JsonProperty("created")] + [JsonConverter(typeof(EpochConverter))] + public DateTime CreatedOn { get; set; } + + [JsonProperty("email")] + public string EmailAddress { get; set; } + + [JsonProperty("reason")] + public string Reason { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Bounces.cs b/SendGrid/SendGrid/Resources/Bounces.cs new file mode 100644 index 000000000..3f90da452 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Bounces.cs @@ -0,0 +1,91 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Bounces + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Bounces object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Bounces(Client client, string endpoint = "v3/suppression/bounces") + { + _endpoint = endpoint; + _client = client; + } + + /// + /// Get a list of bounces + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task GetAsync(DateTime? start = null, DateTime? end = null) + { + var query = HttpUtility.ParseQueryString(string.Empty); + if (start.HasValue) query["start_time"] = start.Value.ToUnixTime().ToString(); + if (end.HasValue) query["end_time"] = end.Value.ToUnixTime().ToString(); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bounces = JArray.Parse(responseContent).ToObject(); + return bounces; + } + + /// + /// Get a list of bounces for a given email address + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task GetAsync(string email) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bounces = JArray.Parse(responseContent).ToObject(); + return bounces; + } + /// + /// Delete all bounces + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAllAsync() + { + var response = await _client.Delete(_endpoint); + response.EnsureSuccess(); + } + + /// + /// Delete bounces for a specified group of email addresses + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAsync(IEnumerable emails) + { + var data = new JObject(new JProperty("emails", new JArray(emails.ToArray()))); + var response = await _client.Delete(_endpoint, data); + response.EnsureSuccess(); + } + + /// + /// Delete bounces for a specified email address + /// + /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html + public async Task DeleteAsync(string email) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, email)); + response.EnsureSuccess(); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index f0a37c1be..ca19567cf 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,17 +64,20 @@ + + + diff --git a/SendGrid/SendGrid/Utilities/EpochConverter.cs b/SendGrid/SendGrid/Utilities/EpochConverter.cs new file mode 100644 index 000000000..138467be8 --- /dev/null +++ b/SendGrid/SendGrid/Utilities/EpochConverter.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace SendGrid.Utilities +{ + public class EpochConverter : DateTimeConverterBase + { + private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) return; + + var secondsSinceEpoch = ((DateTime)value).ToUnixTime(); + serializer.Serialize(writer, secondsSinceEpoch); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) return null; + return _epoch.AddSeconds((long)reader.Value); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/Utilities/Extensions.cs b/SendGrid/SendGrid/Utilities/Extensions.cs index 952a25675..c4fb86674 100644 --- a/SendGrid/SendGrid/Utilities/Extensions.cs +++ b/SendGrid/SendGrid/Utilities/Extensions.cs @@ -6,6 +6,18 @@ namespace SendGrid.Utilities { public static class Extensions { + private static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(this long unixTime) + { + return EPOCH.AddSeconds(unixTime); + } + + public static long ToUnixTime(this DateTime date) + { + return Convert.ToInt64((date.ToUniversalTime() - EPOCH).TotalSeconds); + } + public static string GetDescription(this Enum value) { var fieldInfo = value.GetType().GetField(value.ToString()); From 431ab71d9f36bc74f6c10ed497a5e759d5985f16 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 16:30:23 -0600 Subject: [PATCH 22/52] Strongly typed custom fields --- SendGrid/SendGrid/Client.cs | 2 ++ SendGrid/SendGrid/Model/CustomField.cs | 10 ++++++++++ SendGrid/SendGrid/Model/Field.cs | 15 +++++++++++++++ SendGrid/SendGrid/Model/FieldType.cs | 14 ++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 4 ++++ 5 files changed, 45 insertions(+) create mode 100644 SendGrid/SendGrid/Model/CustomField.cs create mode 100644 SendGrid/SendGrid/Model/Field.cs create mode 100644 SendGrid/SendGrid/Model/FieldType.cs diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 995440486..b614ece6f 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -27,6 +27,7 @@ private enum Methods public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } public Bounces Bounces { get; private set; } + public CustomFields CustomFields { get; private set; } public string Version { get; private set; } /// @@ -46,6 +47,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); Bounces = new Bounces(this); + CustomFields = new CustomFields(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/CustomField.cs b/SendGrid/SendGrid/Model/CustomField.cs new file mode 100644 index 000000000..842952cbe --- /dev/null +++ b/SendGrid/SendGrid/Model/CustomField.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class CustomField : Field + { + [JsonProperty("id")] + public int Id { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/Field.cs b/SendGrid/SendGrid/Model/Field.cs new file mode 100644 index 000000000..1dadf5f8f --- /dev/null +++ b/SendGrid/SendGrid/Model/Field.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace SendGrid.Model +{ + public class Field + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] + public FieldType Type { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/FieldType.cs b/SendGrid/SendGrid/Model/FieldType.cs new file mode 100644 index 000000000..300353e7c --- /dev/null +++ b/SendGrid/SendGrid/Model/FieldType.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace SendGrid.Model +{ + public enum FieldType + { + [EnumMember(Value = "date")] + Date, + [EnumMember(Value = "text")] + Text, + [EnumMember(Value = "number")] + Number + } +} diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index ca19567cf..fdef76e49 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -64,6 +64,9 @@ + + + @@ -72,6 +75,7 @@ + From de04c00e6d501b77991edec109dd73a7413032e6 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Mon, 15 Feb 2016 16:32:39 -0600 Subject: [PATCH 23/52] Display the underlying error message when exception occurs --- SendGrid/SendGrid/Client.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index b614ece6f..115511b3c 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -27,7 +27,9 @@ private enum Methods public GlobalSuppressions GlobalSuppressions { get; private set; } public GlobalStats GlobalStats { get; private set; } public Bounces Bounces { get; private set; } + public User User { get; private set; } public CustomFields CustomFields { get; private set; } + public Contacts Contacts { get; private set; } public string Version { get; private set; } /// @@ -46,6 +48,8 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Suppressions = new Suppressions(this); GlobalSuppressions = new GlobalSuppressions(this); GlobalStats = new GlobalStats(this); + User = new User(this); + Contacts = new Contacts(this); Bounces = new Bounces(this); CustomFields = new CustomFields(this); @@ -120,7 +124,7 @@ private async Task RequestAsync(Methods method, string endp } catch (Exception ex) { - var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.Message); + var message = string.Format(".NET {0}, raw message: \n\n{1}", (ex is HttpRequestException) ? "HttpRequestException" : "Exception", ex.GetBaseException().Message); var response = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(message) From 0285525c77d5497ce11097244e77721c44125791 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 10:47:39 -0600 Subject: [PATCH 24/52] Added missing reference to system serialization --- SendGrid/SendGrid/SendGrid.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index fdef76e49..bb98e90f4 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -53,6 +53,7 @@ ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True + From 8c180872cd3cd378375b835fb2733ebf9d0b824d Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 15:53:25 -0600 Subject: [PATCH 25/52] Strongly typed Lists --- SendGrid/Example/Program.cs | 33 +++++ SendGrid/SendGrid/Client.cs | 2 + SendGrid/SendGrid/Model/List.cs | 16 +++ SendGrid/SendGrid/Resources/Lists.cs | 184 +++++++++++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 2 + 5 files changed, 237 insertions(+) create mode 100644 SendGrid/SendGrid/Model/List.cs create mode 100644 SendGrid/SendGrid/Resources/Lists.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 7fee1e530..9b7c9733a 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -42,6 +42,7 @@ private static void Main() UnsubscribeGroups(httpClient); GlobalSuppressions(httpClient); GlobalStats(httpClient); + Lists(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -236,5 +237,37 @@ private static void GlobalStats(HttpClient httpClient) Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } + + private static void Lists(HttpClient httpClient) + { + Console.WriteLine("\n***** LISTS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + var firstList = client.Lists.CreateAsync("My first list").Result; + Console.WriteLine("List '{0}' created. Id: {1}", firstList.Name, firstList.Id); + + var secondList = client.Lists.CreateAsync("My second list").Result; + Console.WriteLine("List '{0}' created. Id: {1}", secondList.Name, secondList.Id); + + client.Lists.UpdateAsync(firstList.Id, "New name").Wait(); + Console.WriteLine("List '{0}' updated", firstList.Id); + + var lists = client.Lists.GetAllAsync().Result; + Console.WriteLine("All lists retrieved. There are {0} lists", lists.Length); + + client.Lists.DeleteAsync(firstList.Id).Wait(); + Console.WriteLine("List {0} deleted", firstList.Id); + + client.Lists.DeleteAsync(secondList.Id).Wait(); + Console.WriteLine("List {0} deleted", secondList.Id); + + lists = client.Lists.GetAllAsync().Result; + Console.WriteLine("All lists retrieved. There are {0} lists", lists.Length); + + Console.WriteLine("\n\nPress any key to continue"); + Console.ReadKey(); + } } } diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 115511b3c..259dc00be 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -30,6 +30,7 @@ private enum Methods public User User { get; private set; } public CustomFields CustomFields { get; private set; } public Contacts Contacts { get; private set; } + public Lists Lists { get; private set; } public string Version { get; private set; } /// @@ -52,6 +53,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Contacts = new Contacts(this); Bounces = new Bounces(this); CustomFields = new CustomFields(this); + Lists = new Lists(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/List.cs b/SendGrid/SendGrid/Model/List.cs new file mode 100644 index 000000000..7e24ea5fb --- /dev/null +++ b/SendGrid/SendGrid/Model/List.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class List + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("recipient_count")] + public long RecipientCount { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Lists.cs b/SendGrid/SendGrid/Resources/Lists.cs new file mode 100644 index 000000000..b2ba60da3 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Lists.cs @@ -0,0 +1,184 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Lists + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Lists object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/contactdb.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Lists(Client client, string endpoint = "v3/contactdb/lists") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(string name) + { + var data = new JObject() + { + new JProperty("name", name) + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var bulkUpsertResult = JObject.Parse(responseContent).ToObject(); + return bulkUpsertResult; + } + + public async Task GetAllAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "lists": [ + // { + // "id": 1, + // "name": "the jones", + // "recipient_count": 1 + // } + // ] + //} + // We use a dynamic object to get rid of the 'lists' property and simply return an array of lists + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.lists; + + var lists = dynamicArray.ToObject(); + return lists; + } + + public async Task DeleteAsync(long listId) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, listId)); + response.EnsureSuccess(); + } + + public async Task DeleteAsync(IEnumerable recipientIds) + { + var data = new JArray(recipientIds.ToArray()); + var response = await _client.Delete(_endpoint, data); + response.EnsureSuccess(); + } + + public async Task GetAsync(int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "lists": [ + // { + // "id": 1, + // "name": "the jones", + // "recipient_count": 1 + // } + // ] + //} + // We use a dynamic object to get rid of the 'lists' property and simply return an array of lists + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.lists; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task GetAsync(long listId) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, listId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var list = JObject.Parse(responseContent).ToObject(); + return list; + } + + public async Task UpdateAsync(long listId, string name) + { + var data = new JObject() + { + new JProperty("name", name) + }; + var response = await _client.Patch(string.Format("{0}/{1}", _endpoint, listId), data); + response.EnsureSuccess(); + } + + public async Task GetRecipientsAsync(long listId, int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}/{1}/recipients?{2}", _endpoint, listId, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipients": [ + // { + // "created_at": 1422395108, + // "email": "e@example.com", + // "first_name": "Ed", + // "id": "YUBh", + // "last_clicked": null, + // "last_emailed": null, + // "last_name": null, + // "last_opened": null, + // "updated_at": 1422395108 + // } + // ] + // } + // We use a dynamic object to get rid of the 'recipients' property and simply return an array of recipients + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.recipients; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task AddRecipientAsync(long listId, string recipientId) + { + var response = await _client.Post(string.Format("{0}/{1}/recipients/{2}", _endpoint, listId, recipientId), (JObject)null); + response.EnsureSuccess(); + } + + public async Task RemoveRecipientAsync(long listId, string recipientId) + { + var response = await _client.Delete(string.Format("{0}/{1}/recipients/{2}", _endpoint, listId, recipientId)); + response.EnsureSuccess(); + } + + public async Task AddRecipientsAsync(long listId, IEnumerable recipientIds) + { + var data = new JArray(recipientIds.ToArray()); + var response = await _client.Post(string.Format("{0}/{1}/recipients", _endpoint, listId), data); + response.EnsureSuccess(); + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index bb98e90f4..877f2456f 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -69,6 +69,7 @@ + @@ -79,6 +80,7 @@ + From 0f321885f759a9c419eac47601bc57be3feb90b6 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Tue, 16 Feb 2016 16:46:23 -0600 Subject: [PATCH 26/52] Added strongly typed Custom Fields --- SendGrid/Example/Program.cs | 36 +++++++ SendGrid/SendGrid/Resources/CustomFields.cs | 103 ++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 SendGrid/SendGrid/Resources/CustomFields.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 9b7c9733a..adafb4838 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -43,6 +43,7 @@ private static void Main() GlobalSuppressions(httpClient); GlobalStats(httpClient); Lists(httpClient); + CustomFields(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -269,5 +270,40 @@ private static void Lists(HttpClient httpClient) Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } + + private static void CustomFields(HttpClient httpClient) + { + Console.WriteLine("\n***** CUSTOM FIELDS *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + var firstField = client.CustomFields.CreateAsync("first_field", FieldType.Text).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", firstField.Name, firstField.Id); + + var secondField = client.CustomFields.CreateAsync("second_field", FieldType.Number).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", secondField.Name, secondField.Id); + + var thirdField = client.CustomFields.CreateAsync("third field", FieldType.Date).Result; + Console.WriteLine("Field '{0}' created. Id: {1}", thirdField.Name, thirdField.Id); + + var fields = client.CustomFields.GetAllAsync().Result; + Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); + + client.CustomFields.DeleteAsync(firstField.Id).Wait(); + Console.WriteLine("Field {0} deleted", firstField.Id); + + client.CustomFields.DeleteAsync(secondField.Id).Wait(); + Console.WriteLine("Field {0} deleted", secondField.Id); + + client.CustomFields.DeleteAsync(thirdField.Id).Wait(); + Console.WriteLine("Field {0} deleted", thirdField.Id); + + fields = client.CustomFields.GetAllAsync().Result; + Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); + + Console.WriteLine("\n\nPress any key to continue"); + Console.ReadKey(); + } } } diff --git a/SendGrid/SendGrid/Resources/CustomFields.cs b/SendGrid/SendGrid/Resources/CustomFields.cs new file mode 100644 index 000000000..da58575bd --- /dev/null +++ b/SendGrid/SendGrid/Resources/CustomFields.cs @@ -0,0 +1,103 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Threading.Tasks; + +namespace SendGrid.Resources +{ + public class CustomFields + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Recipients object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/contactdb.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public CustomFields(Client client, string endpoint = "v3/contactdb/custom_fields") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(string name, FieldType type) + { + var data = new JObject() + { + { "name", name }, + { "type", JToken.Parse(JsonConvert.SerializeObject(type, Formatting.None, new StringEnumConverter())).Value() } + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var field = JObject.Parse(responseContent).ToObject(); + return field; + } + + public async Task GetAllAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + //{ + // "custom_fields": [ + // { + // "id": 1, + // "name": "birthday", + // "type": "date" + // }, + // { + // "id": 2, + // "name": "middle_name", + // "type": "text" + // }, + // { + // "id": 3, + // "name": "favorite_number", + // "type": "number" + // } + // ] + //} + // We use a dynamic object to get rid of the 'custom_fields' property and simply return an array of custom fields + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.custom_fields; + + var fields = dynamicArray.ToObject(); + return fields; + } + + public async Task GetAsync(int fieldId) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, fieldId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var field = JObject.Parse(responseContent).ToObject(); + return field; + } + + public async Task DeleteAsync(int fieldId) + { + var response = await _client.Delete(string.Format("{0}/{1}", _endpoint, fieldId)); + response.EnsureSuccess(); + } + + public async Task GetReservedFieldsAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var fields = JArray.Parse(responseContent).ToObject(); + return fields; + } + } +} \ No newline at end of file From 7134d77844981da3448ae2518ffb0864713cd03a Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 17 Feb 2016 15:16:09 -0600 Subject: [PATCH 27/52] Strongly typed Segments --- SendGrid/Example/Program.cs | 20 ++- SendGrid/SendGrid/Client.cs | 2 + SendGrid/SendGrid/Model/Condition.cs | 22 +++ .../Model/ConditionLogicalConjunction.cs | 14 ++ SendGrid/SendGrid/Model/ConditionOperator.cs | 18 +++ SendGrid/SendGrid/Model/Segment.cs | 19 +++ SendGrid/SendGrid/Resources/Segments.cs | 152 ++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 5 + 8 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 SendGrid/SendGrid/Model/Condition.cs create mode 100644 SendGrid/SendGrid/Model/ConditionLogicalConjunction.cs create mode 100644 SendGrid/SendGrid/Model/ConditionOperator.cs create mode 100644 SendGrid/SendGrid/Model/Segment.cs create mode 100644 SendGrid/SendGrid/Resources/Segments.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index adafb4838..1b919de4d 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -37,12 +37,12 @@ private static void Main() var from = "example@example.com"; var fromName = "Jane Doe"; SendEmail(to, from, fromName); - // Test viewing, creating, modifying and deleting API keys through our v3 Web API + ApiKeys(httpClient); UnsubscribeGroups(httpClient); GlobalSuppressions(httpClient); GlobalStats(httpClient); - Lists(httpClient); + ListsAndSegments(httpClient); CustomFields(httpClient); } @@ -239,9 +239,9 @@ private static void GlobalStats(HttpClient httpClient) Console.ReadKey(); } - private static void Lists(HttpClient httpClient) + private static void ListsAndSegments(HttpClient httpClient) { - Console.WriteLine("\n***** LISTS *****"); + Console.WriteLine("\n***** LISTS AND SEGMENTS *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); @@ -258,6 +258,18 @@ private static void Lists(HttpClient httpClient) var lists = client.Lists.GetAllAsync().Result; Console.WriteLine("All lists retrieved. There are {0} lists", lists.Length); + var hotmailCondition = new Condition() { Field = "email", Operator = ConditionOperator.Contains, Value = "hotmail.com", AndOr = ConditionLogicalConjunction.None }; + var segment = client.Segments.CreateAsync("Recipients @ Hotmail", firstList.Id, new[] { hotmailCondition }).Result; + Console.WriteLine("Segment '{0}' created. Id: {1}", segment.Name, segment.Id); + + var millerLastNameCondition = new Condition() { Field = "last_name", Operator = ConditionOperator.Equal, Value = "Miller", AndOr = ConditionLogicalConjunction.None }; + var clickedRecentlyCondition = new Condition() { Field = "last_clicked", Operator = ConditionOperator.GreaterThan, Value = DateTime.UtcNow.AddDays(-30).ToString("MM/dd/yyyy"), AndOr = ConditionLogicalConjunction.And }; + segment = client.Segments.UpdateAsync(segment.Id, "Last Name is Miller and clicked recently", null, new[] { millerLastNameCondition, clickedRecentlyCondition }).Result; + Console.WriteLine("Segment {0} updated. The new name is: '{1}'", segment.Id, segment.Name); + + client.Segments.DeleteAsync(segment.Id).Wait(); + Console.WriteLine("Segment {0} deleted", segment.Id); + client.Lists.DeleteAsync(firstList.Id).Wait(); Console.WriteLine("List {0} deleted", firstList.Id); diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 259dc00be..929468606 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -31,6 +31,7 @@ private enum Methods public CustomFields CustomFields { get; private set; } public Contacts Contacts { get; private set; } public Lists Lists { get; private set; } + public Segments Segments { get; private set; } public string Version { get; private set; } /// @@ -54,6 +55,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC Bounces = new Bounces(this); CustomFields = new CustomFields(this); Lists = new Lists(this); + Segments = new Segments(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/Condition.cs b/SendGrid/SendGrid/Model/Condition.cs new file mode 100644 index 000000000..1e656cde6 --- /dev/null +++ b/SendGrid/SendGrid/Model/Condition.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace SendGrid.Model +{ + public class Condition + { + [JsonProperty("field")] + public string Field { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("operator")] + [JsonConverter(typeof(StringEnumConverter))] + public ConditionOperator Operator { get; set; } + + [JsonProperty("and_or")] + [JsonConverter(typeof(StringEnumConverter))] + public ConditionLogicalConjunction AndOr { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/ConditionLogicalConjunction.cs b/SendGrid/SendGrid/Model/ConditionLogicalConjunction.cs new file mode 100644 index 000000000..e127ae4d1 --- /dev/null +++ b/SendGrid/SendGrid/Model/ConditionLogicalConjunction.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace SendGrid.Model +{ + public enum ConditionLogicalConjunction + { + [EnumMember(Value = "")] + None, + [EnumMember(Value = "and")] + And, + [EnumMember(Value = "or")] + Or + } +} diff --git a/SendGrid/SendGrid/Model/ConditionOperator.cs b/SendGrid/SendGrid/Model/ConditionOperator.cs new file mode 100644 index 000000000..ff0a848e2 --- /dev/null +++ b/SendGrid/SendGrid/Model/ConditionOperator.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace SendGrid.Model +{ + public enum ConditionOperator + { + [EnumMember(Value = "contains")] + Contains, + [EnumMember(Value = "eq")] + Equal, + [EnumMember(Value = "ne")] + NotEqual, + [EnumMember(Value = "lt")] + LessThan, + [EnumMember(Value = "gt")] + GreaterThan + } +} diff --git a/SendGrid/SendGrid/Model/Segment.cs b/SendGrid/SendGrid/Model/Segment.cs new file mode 100644 index 000000000..dd6a31c43 --- /dev/null +++ b/SendGrid/SendGrid/Model/Segment.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Segment + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("list_id")] + public long ListId { get; set; } + + [JsonProperty("conditions")] + public Condition[] Conditions { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Segments.cs b/SendGrid/SendGrid/Resources/Segments.cs new file mode 100644 index 000000000..475644683 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Segments.cs @@ -0,0 +1,152 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Segments + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Campaigns object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/campaigns.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Segments(Client client, string endpoint = "v3/contactdb/segments") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(string name, long listId, IEnumerable conditions) + { + conditions = (conditions ?? Enumerable.Empty()); + + var data = new JObject() + { + { "name", name }, + { "list_id", listId }, + { "conditions", JArray.FromObject(conditions.ToArray()) } + }; + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var segment = JObject.Parse(responseContent).ToObject(); + return segment; + } + + public async Task GetAllAsync() + { + var response = await _client.Get(_endpoint); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "segments": [ + // { + // "id": 1, + // "name": "Last Name Miller", + // "list_id": 4, + // "conditions": [ + // { + // "field": "last_name", + // "value": "Miller", + // "operator": "eq", + // "and_or": "" + // } + // ], + // "recipient_count": 1 + // } + // ] + // } + // We use a dynamic object to get rid of the 'segments' property and simply return an array of segments + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.segments; + + var segments = dynamicArray.ToObject(); + return segments; + } + + public async Task GetAsync(long segmentId) + { + var response = await _client.Get(string.Format("{0}/{1}", _endpoint, segmentId)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var segment = JObject.Parse(responseContent).ToObject(); + return segment; + } + + public async Task UpdateAsync(long segmentId, string name = null, long? listId = null, IEnumerable conditions = null) + { + conditions = (conditions ?? Enumerable.Empty()); + + var data = new JObject(); + if (!string.IsNullOrEmpty(name)) data.Add("name", name); + if (listId.HasValue) data.Add("list_id", listId.Value); + if (conditions.Any()) data.Add("conditions", JArray.FromObject(conditions.ToArray())); + + var response = await _client.Patch(string.Format("{0}/{1}", _endpoint, segmentId), data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var segment = JObject.Parse(responseContent).ToObject(); + return segment; + } + + public async Task DeleteAsync(long segmentId, bool deleteMatchingContacts = false) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["delete_contacts"] = (deleteMatchingContacts ? "true" : "false"); + + var response = await _client.Delete(string.Format("{0}/{1}?{2}", _endpoint, segmentId, query)); + response.EnsureSuccess(); + } + + public async Task GetRecipientsAsync(long segmentId, int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipients": [ + // { + // "created_at": 1422395108, + // "email": "e@example.com", + // "first_name": "Ed", + // "id": "YUBh", + // "last_clicked": null, + // "last_emailed": null, + // "last_name": null, + // "last_opened": null, + // "updated_at": 1422395108 + // } + // ] + // } + // We use a dynamic object to get rid of the 'recipients' property and simply return an array of recipients + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.recipients; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index 877f2456f..bb7ce9116 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -65,10 +65,14 @@ + + + + @@ -81,6 +85,7 @@ + From 158d23b529d6be84d42030ddde04f0a72af2cf4f Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Wed, 17 Feb 2016 17:25:53 -0600 Subject: [PATCH 28/52] Replace 'new JArray(...)' with JArray.FromObject(...)' Most of the time, 'new JArray(...)' is fine but there are a few exceptions (such as where serializing the Versions property of the Template class) therefore I decided to standardize on JArray.FromObject --- SendGrid/SendGrid/Resources/Bounces.cs | 2 +- SendGrid/SendGrid/Resources/GlobalSuppressions.cs | 2 +- SendGrid/SendGrid/Resources/Lists.cs | 8 ++++---- SendGrid/SendGrid/Resources/Suppressions.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SendGrid/SendGrid/Resources/Bounces.cs b/SendGrid/SendGrid/Resources/Bounces.cs index 3f90da452..0e67bcfad 100644 --- a/SendGrid/SendGrid/Resources/Bounces.cs +++ b/SendGrid/SendGrid/Resources/Bounces.cs @@ -73,7 +73,7 @@ public async Task DeleteAllAsync() /// https://sendgrid.com/docs/API_Reference/Web_API_v3/bounces.html public async Task DeleteAsync(IEnumerable emails) { - var data = new JObject(new JProperty("emails", new JArray(emails.ToArray()))); + var data = new JObject(new JProperty("emails", JArray.FromObject(emails.ToArray()))); var response = await _client.Delete(_endpoint, data); response.EnsureSuccess(); } diff --git a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs index c31cd716f..a413455a9 100644 --- a/SendGrid/SendGrid/Resources/GlobalSuppressions.cs +++ b/SendGrid/SendGrid/Resources/GlobalSuppressions.cs @@ -55,7 +55,7 @@ public async Task IsUnsubscribedAsync(string email) /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/global_suppressions.html public async Task AddAsync(IEnumerable emails) { - var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); + var data = new JObject(new JProperty("recipient_emails", JArray.FromObject(emails.ToArray()))); var response = await _client.Post(_endpoint, data); response.EnsureSuccess(); } diff --git a/SendGrid/SendGrid/Resources/Lists.cs b/SendGrid/SendGrid/Resources/Lists.cs index b2ba60da3..960fd7661 100644 --- a/SendGrid/SendGrid/Resources/Lists.cs +++ b/SendGrid/SendGrid/Resources/Lists.cs @@ -73,7 +73,7 @@ public async Task DeleteAsync(long listId) public async Task DeleteAsync(IEnumerable recipientIds) { - var data = new JArray(recipientIds.ToArray()); + var data = JArray.FromObject(recipientIds.ToArray()); var response = await _client.Delete(_endpoint, data); response.EnsureSuccess(); } @@ -127,7 +127,7 @@ public async Task UpdateAsync(long listId, string name) response.EnsureSuccess(); } - public async Task GetRecipientsAsync(long listId, int recordsPerPage = 100, int page = 1) + public async Task GetRecipientsAsync(long listId, int recordsPerPage = 100, int page = 1) { var query = HttpUtility.ParseQueryString(string.Empty); query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); @@ -158,7 +158,7 @@ public async Task GetRecipientsAsync(long listId, int recordsPerPag dynamic dynamicObject = JObject.Parse(responseContent); dynamic dynamicArray = dynamicObject.recipients; - var recipients = dynamicArray.ToObject(); + var recipients = dynamicArray.ToObject(); return recipients; } @@ -176,7 +176,7 @@ public async Task RemoveRecipientAsync(long listId, string recipientId) public async Task AddRecipientsAsync(long listId, IEnumerable recipientIds) { - var data = new JArray(recipientIds.ToArray()); + var data = JArray.FromObject(recipientIds.ToArray()); var response = await _client.Post(string.Format("{0}/{1}/recipients", _endpoint, listId), data); response.EnsureSuccess(); } diff --git a/SendGrid/SendGrid/Resources/Suppressions.cs b/SendGrid/SendGrid/Resources/Suppressions.cs index 523b52092..6b13c7105 100644 --- a/SendGrid/SendGrid/Resources/Suppressions.cs +++ b/SendGrid/SendGrid/Resources/Suppressions.cs @@ -61,7 +61,7 @@ public async Task AddAddressToUnsubscribeGroupAsync(int groupId, string email) /// https://sendgrid.com/docs/API_Reference/Web_API_v3/Suppression_Management/suppressions.html public async Task AddAddressToUnsubscribeGroupAsync(int groupId, IEnumerable emails) { - var data = new JObject(new JProperty("recipient_emails", new JArray(emails.ToArray()))); + var data = new JObject(new JProperty("recipient_emails", JArray.FromObject(emails.ToArray()))); var response = await _client.Post(string.Format("{0}/{1}/suppressions", _endpoint, groupId), data); response.EnsureSuccess(); } From a41801bd1e30ed65112fa751ae49731697d7cea8 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Sat, 20 Feb 2016 15:40:42 -0600 Subject: [PATCH 29/52] Stringly typed Contacts --- SendGrid/Example/Program.cs | 68 ++++-- SendGrid/SendGrid/Client.cs | 8 + SendGrid/SendGrid/Model/CustomField.cs | 6 +- .../SendGrid/Model/CustomFieldMetadata.cs | 10 + SendGrid/SendGrid/Resources/Contacts.cs | 231 ++++++++++++++++++ SendGrid/SendGrid/Resources/CustomFields.cs | 12 +- SendGrid/SendGrid/SendGrid.csproj | 7 +- 7 files changed, 316 insertions(+), 26 deletions(-) create mode 100644 SendGrid/SendGrid/Model/CustomFieldMetadata.cs create mode 100644 SendGrid/SendGrid/Resources/Contacts.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 1b919de4d..5daabfdb8 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -2,6 +2,7 @@ using SendGrid.Model; using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Mail; @@ -43,7 +44,7 @@ private static void Main() GlobalSuppressions(httpClient); GlobalStats(httpClient); ListsAndSegments(httpClient); - CustomFields(httpClient); + ContactsAndCustomFields(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -283,33 +284,68 @@ private static void ListsAndSegments(HttpClient httpClient) Console.ReadKey(); } - private static void CustomFields(HttpClient httpClient) + private static void ContactsAndCustomFields(HttpClient httpClient) { - Console.WriteLine("\n***** CUSTOM FIELDS *****"); + Console.WriteLine("\n***** CONTACTS AND CUSTOM FIELDS *****"); var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); - var firstField = client.CustomFields.CreateAsync("first_field", FieldType.Text).Result; - Console.WriteLine("Field '{0}' created. Id: {1}", firstField.Name, firstField.Id); + var fields = client.CustomFields.GetAllAsync().Result; + Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); - var secondField = client.CustomFields.CreateAsync("second_field", FieldType.Number).Result; - Console.WriteLine("Field '{0}' created. Id: {1}", secondField.Name, secondField.Id); + CustomFieldMetadata nicknameField; + if (fields.Any(f => f.Name == "nickname")) nicknameField = fields.Single(f => f.Name == "nickname"); + else nicknameField = client.CustomFields.CreateAsync("nickname", FieldType.Text).Result; + Console.WriteLine("Field '{0}' Id: {1}", nicknameField.Name, nicknameField.Id); - var thirdField = client.CustomFields.CreateAsync("third field", FieldType.Date).Result; - Console.WriteLine("Field '{0}' created. Id: {1}", thirdField.Name, thirdField.Id); + CustomFieldMetadata ageField; + if (fields.Any(f => f.Name == "age")) ageField = fields.Single(f => f.Name == "age"); + else ageField = client.CustomFields.CreateAsync("age", FieldType.Number).Result; + Console.WriteLine("Field '{0}' Id: {1}", ageField.Name, ageField.Id); - var fields = client.CustomFields.GetAllAsync().Result; + CustomFieldMetadata customerSinceField; + if (fields.Any(f => f.Name == "customer_since")) customerSinceField = fields.Single(f => f.Name == "customer_since"); + else customerSinceField = client.CustomFields.CreateAsync("customer_since", FieldType.Date).Result; + Console.WriteLine("Field '{0}' Id: {1}", customerSinceField.Name, customerSinceField.Id); + + fields = client.CustomFields.GetAllAsync().Result; Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); - client.CustomFields.DeleteAsync(firstField.Id).Wait(); - Console.WriteLine("Field {0} deleted", firstField.Id); + var contact1 = new Contact() + { + Email = "111@example.com", + FirstName = "Robert", + LastName = "Unknown", + CustomFields = new CustomFieldMetadata[] + { + new CustomField() { Name = "nickname", Value = "Bob" }, + new CustomField() { Name = "age", Value = 42 }, + new CustomField() { Name = "customer_since", Value = new DateTime(2000, 12, 1) }, + } + }; + contact1.Id = client.Contacts.CreateAsync(contact1).Result; + Console.WriteLine("{0} {1} created. Id: {2}", contact1.FirstName, contact1.LastName, contact1.Id); + + var contact2 = new Contact() + { + Email = "111@example.com", + LastName = "Smith" + }; + contact1.Id = client.Contacts.UpdateAsync(contact2).Result; + Console.WriteLine("{0} {1} updated. Id: {2}", contact1.FirstName, contact2.LastName, contact1.Id); + + client.Contacts.DeleteAsync(contact1.Id).Wait(); + Console.WriteLine("{0} {1} deleted. Id: {2}", contact1.FirstName, contact2.LastName, contact1.Id); + + client.CustomFields.DeleteAsync(nicknameField.Id).Wait(); + Console.WriteLine("Field {0} deleted", nicknameField.Id); - client.CustomFields.DeleteAsync(secondField.Id).Wait(); - Console.WriteLine("Field {0} deleted", secondField.Id); + client.CustomFields.DeleteAsync(ageField.Id).Wait(); + Console.WriteLine("Field {0} deleted", ageField.Id); - client.CustomFields.DeleteAsync(thirdField.Id).Wait(); - Console.WriteLine("Field {0} deleted", thirdField.Id); + client.CustomFields.DeleteAsync(customerSinceField.Id).Wait(); + Console.WriteLine("Field {0} deleted", customerSinceField.Id); fields = client.CustomFields.GetAllAsync().Result; Console.WriteLine("All custom fields retrieved. There are {0} fields", fields.Length); diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 929468606..4b198038e 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -190,5 +190,13 @@ public async Task Patch(string endpoint, JObject data) { return await RequestAsync(Methods.PATCH, endpoint, data); } + + /// Resource endpoint, do not prepend slash + /// An JArray representing the resource's data + /// The resulting message from the API call + public async Task Patch(string endpoint, JArray data) + { + return await RequestAsync(Methods.PATCH, endpoint, data); + } } } diff --git a/SendGrid/SendGrid/Model/CustomField.cs b/SendGrid/SendGrid/Model/CustomField.cs index 842952cbe..f293c6870 100644 --- a/SendGrid/SendGrid/Model/CustomField.cs +++ b/SendGrid/SendGrid/Model/CustomField.cs @@ -2,9 +2,9 @@ namespace SendGrid.Model { - public class CustomField : Field + public class CustomField : CustomFieldMetadata { - [JsonProperty("id")] - public int Id { get; set; } + [JsonProperty("value")] + public T Value { get; set; } } } diff --git a/SendGrid/SendGrid/Model/CustomFieldMetadata.cs b/SendGrid/SendGrid/Model/CustomFieldMetadata.cs new file mode 100644 index 000000000..7c5e27da8 --- /dev/null +++ b/SendGrid/SendGrid/Model/CustomFieldMetadata.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class CustomFieldMetadata : Field + { + [JsonProperty("id")] + public int Id { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Contacts.cs b/SendGrid/SendGrid/Resources/Contacts.cs new file mode 100644 index 000000000..965f63aeb --- /dev/null +++ b/SendGrid/SendGrid/Resources/Contacts.cs @@ -0,0 +1,231 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace SendGrid.Resources +{ + public class Contacts + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Recipients object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Marketing_Campaigns/contactdb.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Contacts(Client client, string endpoint = "v3/contactdb/recipients") + { + _endpoint = endpoint; + _client = client; + } + + public async Task CreateAsync(Contact contact) + { + var importResult = await ImportAsync(new[] { contact }); + if (importResult.ErrorCount > 0) + { + // There should only be one error message but to be safe let's combine all error messages into a single string + var errorMsg = string.Join(Environment.NewLine, importResult.Errors.Select(e => e.Message)); + throw new Exception(errorMsg); + } + return importResult.PersistedRecipients.Single(); + } + + public async Task UpdateAsync(Contact contact) + { + var data = new JArray(ConvertContactToJObject(contact)); + var response = await _client.Patch(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var importResult = JObject.Parse(responseContent).ToObject(); + if (importResult.ErrorCount > 0) + { + // There should only be one error message but to be safe let's combine all error messages into a single string + var errorMsg = string.Join(Environment.NewLine, importResult.Errors.Select(e => e.Message)); + throw new Exception(errorMsg); + } + return importResult.PersistedRecipients.Single(); + } + + public async Task ImportAsync(IEnumerable contacts) + { + var data = new JArray(); + foreach (var contact in contacts) + { + data.Add(ConvertContactToJObject(contact)); + } + + var response = await _client.Post(_endpoint, data); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + var importResult = JObject.Parse(responseContent).ToObject(); + return importResult; + } + + public async Task DeleteAsync(string contactId) + { + await DeleteAsync(new[] { contactId }); + } + + public async Task DeleteAsync(IEnumerable contactId) + { + var data = JArray.FromObject(contactId.ToArray()); + var response = await _client.Delete(_endpoint, data); + response.EnsureSuccess(); + } + + public async Task GetAsync(int recordsPerPage = 100, int page = 1) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["page_size"] = recordsPerPage.ToString(CultureInfo.InvariantCulture); + query["page"] = page.ToString(CultureInfo.InvariantCulture); + + var response = await _client.Get(string.Format("{0}?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipients": [ + // { + // "created_at": 1422395108, + // "email": "e@example.com", + // "first_name": "Ed", + // "id": "YUBh", + // "last_clicked": null, + // "last_emailed": null, + // "last_name": null, + // "last_opened": null, + // "updated_at": 1422395108 + // } + // ] + // } + // We use a dynamic object to get rid of the 'recipients' property and simply return an array of recipients + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.recipients; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task GetBillableCountAsync() + { + var response = await _client.Get(string.Format("{0}/billable_count", _endpoint)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipient_count": 2 + // } + // We use a dynamic object to get rid of the 'recipient_count' property and simply return the numerical value + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicValue = dynamicObject.recipient_count; + + var count = dynamicValue.ToObject(); + return count; + } + + public async Task GetTotalCountAsync() + { + var response = await _client.Get(string.Format("{0}/count", _endpoint)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipient_count": 2 + // } + // We use a dynamic object to get rid of the 'recipient_count' property and simply return the numerical value + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicValue = dynamicObject.recipient_count; + + var count = dynamicValue.ToObject(); + return count; + } + + public async Task SearchAsync(string fieldName, string value) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query[fieldName] = value; + + var response = await _client.Get(string.Format("{0}/search?{1}", _endpoint, query)); + response.EnsureSuccess(); + + var responseContent = await response.Content.ReadAsStringAsync(); + + // Response looks like this: + // { + // "recipients": [ + // { + // "created_at": 1422395108, + // "email": "e@example.com", + // "first_name": "Ed", + // "id": "YUBh", + // "last_clicked": null, + // "last_emailed": null, + // "last_name": null, + // "last_opened": null, + // "updated_at": 1422395108 + // } + // ] + // } + // We use a dynamic object to get rid of the 'recipients' property and simply return an array of recipients + dynamic dynamicObject = JObject.Parse(responseContent); + dynamic dynamicArray = dynamicObject.recipients; + + var recipients = dynamicArray.ToObject(); + return recipients; + } + + public async Task SearchAsync(string fieldName, DateTime value) + { + return await SearchAsync(fieldName, value.ToUnixTime().ToString(CultureInfo.InvariantCulture)); + } + + public async Task SearchAsync(string fieldName, long value) + { + return await SearchAsync(fieldName, value.ToString(CultureInfo.InvariantCulture)); + } + + private static JObject ConvertContactToJObject(Contact contact) + { + var result = new JObject(); + if (!string.IsNullOrEmpty(contact.Id)) result.Add("id", contact.Id); + if (!string.IsNullOrEmpty(contact.Email)) result.Add("email", contact.Email); + if (!string.IsNullOrEmpty(contact.FirstName)) result.Add("first_name", contact.FirstName); + if (!string.IsNullOrEmpty(contact.LastName)) result.Add("last_name", contact.LastName); + + if (contact.CustomFields != null) + { + foreach (var customField in contact.CustomFields.OfType>()) + { + result.Add(customField.Name, customField.Value); + } + foreach (var customField in contact.CustomFields.OfType>()) + { + result.Add(customField.Name, customField.Value); + } + foreach (var customField in contact.CustomFields.OfType>()) + { + result.Add(customField.Name, customField.Value.ToUnixTime()); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/SendGrid/SendGrid/Resources/CustomFields.cs b/SendGrid/SendGrid/Resources/CustomFields.cs index da58575bd..abdfea49a 100644 --- a/SendGrid/SendGrid/Resources/CustomFields.cs +++ b/SendGrid/SendGrid/Resources/CustomFields.cs @@ -24,7 +24,7 @@ public CustomFields(Client client, string endpoint = "v3/contactdb/custom_fields _client = client; } - public async Task CreateAsync(string name, FieldType type) + public async Task CreateAsync(string name, FieldType type) { var data = new JObject() { @@ -35,11 +35,11 @@ public async Task CreateAsync(string name, FieldType type) response.EnsureSuccess(); var responseContent = await response.Content.ReadAsStringAsync(); - var field = JObject.Parse(responseContent).ToObject(); + var field = JObject.Parse(responseContent).ToObject(); return field; } - public async Task GetAllAsync() + public async Task GetAllAsync() { var response = await _client.Get(_endpoint); response.EnsureSuccess(); @@ -70,17 +70,17 @@ public async Task GetAllAsync() dynamic dynamicObject = JObject.Parse(responseContent); dynamic dynamicArray = dynamicObject.custom_fields; - var fields = dynamicArray.ToObject(); + var fields = dynamicArray.ToObject(); return fields; } - public async Task GetAsync(int fieldId) + public async Task GetAsync(int fieldId) { var response = await _client.Get(string.Format("{0}/{1}", _endpoint, fieldId)); response.EnsureSuccess(); var responseContent = await response.Content.ReadAsStringAsync(); - var field = JObject.Parse(responseContent).ToObject(); + var field = JObject.Parse(responseContent).ToObject(); return field; } diff --git a/SendGrid/SendGrid/SendGrid.csproj b/SendGrid/SendGrid/SendGrid.csproj index bb7ce9116..cd951e172 100644 --- a/SendGrid/SendGrid/SendGrid.csproj +++ b/SendGrid/SendGrid/SendGrid.csproj @@ -66,14 +66,18 @@ - + + + + + @@ -84,6 +88,7 @@ + From 3e776eaa430cb04045376cc56b28fdfe789516a8 Mon Sep 17 00:00:00 2001 From: Jeremie Desautels Date: Thu, 25 Feb 2016 13:16:16 -0600 Subject: [PATCH 30/52] Strongly typed Templates and Template Versions --- SendGrid/Example/Program.cs | 41 ++++- SendGrid/SendGrid/Client.cs | 2 + SendGrid/SendGrid/Model/Template.cs | 16 ++ SendGrid/SendGrid/Model/TemplateVersion.cs | 33 ++++ SendGrid/SendGrid/Resources/Templates.cs | 163 ++++++++++++++++++ SendGrid/SendGrid/SendGrid.csproj | 5 + .../Utilities/IntegerBooleanConverter.cs | 26 +++ .../Utilities/SendGridDateTimeConverter.cs | 30 ++++ 8 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 SendGrid/SendGrid/Model/Template.cs create mode 100644 SendGrid/SendGrid/Model/TemplateVersion.cs create mode 100644 SendGrid/SendGrid/Resources/Templates.cs create mode 100644 SendGrid/SendGrid/Utilities/IntegerBooleanConverter.cs create mode 100644 SendGrid/SendGrid/Utilities/SendGridDateTimeConverter.cs diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 5daabfdb8..0b6a07f16 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -45,6 +45,7 @@ private static void Main() GlobalStats(httpClient); ListsAndSegments(httpClient); ContactsAndCustomFields(httpClient); + Templates(httpClient); } private static void SendAsync(SendGrid.SendGridMessage message) @@ -353,5 +354,43 @@ private static void ContactsAndCustomFields(HttpClient httpClient) Console.WriteLine("\n\nPress any key to continue"); Console.ReadKey(); } + + private static void Templates(HttpClient httpClient) + { + Console.WriteLine("\n***** TEMPLATES *****"); + + var apiKey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY", EnvironmentVariableTarget.User); + var client = new SendGrid.Client(apiKey: apiKey, httpClient: httpClient); + + var template = client.Templates.CreateAsync("My template").Result; + Console.WriteLine("Template '{0}' created. Id: {1}", template.Name, template.Id); + + client.Templates.UpdateAsync(template.Id, "New name").Wait(); + Console.WriteLine("Template '{0}' updated", template.Id); + + template = client.Templates.GetAsync(template.Id).Result; + Console.WriteLine("Template '{0}' retrieved.", template.Id); + + var firstVersion = client.Templates.CreateVersionAsync(template.Id, "Version 1", "My first Subject <%subject%>", "hello world
<%body%>", "Hello world <%body%>", true).Result; + Console.WriteLine("First version created. Id: {0}", firstVersion.Id); + + var secondVersion = client.Templates.CreateVersionAsync(template.Id, "Version 2", "My second Subject <%subject%>", "Qwerty
<%body%>", "Qwerty <%body%>", true).Result; + Console.WriteLine("Second version created. Id: {0}", secondVersion.Id); + + var templates = client.Templates.GetAllAsync().Result; + Console.WriteLine("All templates retrieved. There are {0} templates", templates.Length); + + client.Templates.DeleteVersionAsync(template.Id, firstVersion.Id).Wait(); + Console.WriteLine("Version {0} deleted", firstVersion.Id); + + client.Templates.DeleteVersionAsync(template.Id, secondVersion.Id).Wait(); + Console.WriteLine("Version {0} deleted", secondVersion.Id); + + client.Templates.DeleteAsync(template.Id).Wait(); + Console.WriteLine("Template {0} deleted", template.Id); + + Console.WriteLine("\n\nPress any key to continue"); + Console.ReadKey(); + } } -} +} \ No newline at end of file diff --git a/SendGrid/SendGrid/Client.cs b/SendGrid/SendGrid/Client.cs index 4b198038e..35b861a3a 100644 --- a/SendGrid/SendGrid/Client.cs +++ b/SendGrid/SendGrid/Client.cs @@ -32,6 +32,7 @@ private enum Methods public Contacts Contacts { get; private set; } public Lists Lists { get; private set; } public Segments Segments { get; private set; } + public Templates Templates { get; private set; } public string Version { get; private set; } /// @@ -56,6 +57,7 @@ public Client(string apiKey, string baseUri = "https://api.sendgrid.com/", HttpC CustomFields = new CustomFields(this); Lists = new Lists(this); Segments = new Segments(this); + Templates = new Templates(this); _httpClient = httpClient ?? new HttpClient(); _httpClient.BaseAddress = _baseUri; diff --git a/SendGrid/SendGrid/Model/Template.cs b/SendGrid/SendGrid/Model/Template.cs new file mode 100644 index 000000000..5ee7c65e8 --- /dev/null +++ b/SendGrid/SendGrid/Model/Template.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace SendGrid.Model +{ + public class Template + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("versions")] + public TemplateVersion[] Versions { get; set; } + } +} diff --git a/SendGrid/SendGrid/Model/TemplateVersion.cs b/SendGrid/SendGrid/Model/TemplateVersion.cs new file mode 100644 index 000000000..72fabc6ac --- /dev/null +++ b/SendGrid/SendGrid/Model/TemplateVersion.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using SendGrid.Utilities; +using System; + +namespace SendGrid.Model +{ + public class TemplateVersion + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("template_id")] + public string TemplateId { get; set; } + + [JsonProperty("active")] + [JsonConverter(typeof(IntegerBooleanConverter))] + public bool IsActive { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("html_content")] + public string HtmlContent { get; set; } + + [JsonProperty("plain_content")] + public string TextContent { get; set; } + + [JsonProperty("updated_at")] + [JsonConverter(typeof(SendGridDateTimeConverter))] + public DateTime UpdatedOn { get; set; } + } +} diff --git a/SendGrid/SendGrid/Resources/Templates.cs b/SendGrid/SendGrid/Resources/Templates.cs new file mode 100644 index 000000000..62b103573 --- /dev/null +++ b/SendGrid/SendGrid/Resources/Templates.cs @@ -0,0 +1,163 @@ +using Newtonsoft.Json.Linq; +using SendGrid.Model; +using SendGrid.Utilities; +using System.Threading.Tasks; + +namespace SendGrid.Resources +{ + public class Templates + { + private string _endpoint; + private Client _client; + + /// + /// Constructs the SendGrid Templates object. + /// See https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/templates.html + /// + /// SendGrid Web API v3 client + /// Resource endpoint, do not prepend slash + public Templates(Client client, string endpoint = "v3/templates") + { + _endpoint = endpoint; + _client = client; + } + + public async Task