diff --git a/USE_CASES.md b/USE_CASES.md index 90ca5d868..5d064f452 100644 --- a/USE_CASES.md +++ b/USE_CASES.md @@ -28,10 +28,11 @@ namespace Example { private static void Main() { - Execute().Wait(); + ExecuteManualAttachmentAdd().Wait(); + ExecuteStreamAttachmentAdd().Wait(); } - static async Task Execute() + static async Task ExecuteManualAttachmentAdd() { var apiKey = Environment.GetEnvironmentVariable("NAME_OF_THE_ENVIRONMENT_VARIABLE_FOR_YOUR_SENDGRID_KEY"); var client = new SendGridClient(apiKey); @@ -45,6 +46,23 @@ namespace Example msg.AddAttachment("file.txt", file); var response = await client.SendEmailAsync(msg); } + + static async Task ExecuteStreamAttachmentAdd() + { + var apiKey = Environment.GetEnvironmentVariable("NAME_OF_THE_ENVIRONMENT_VARIABLE_FOR_YOUR_SENDGRID_KEY"); + var client = new SendGridClient(apiKey); + var from = new EmailAddress("test@example.com"); + var subject = "Subject"; + var to = new EmailAddress("test@example.com"); + var body = "Email Body"; + var msg = MailHelper.CreateSingleEmail(from, to, subject, body, ""); + + using (var fileStream = File.OpenRead("/Users/username/file.txt")) + { + msg.AddAttachment("file.txt", fileStream); + var response = await client.SendEmailAsync(msg); + } + } } } ``` @@ -656,4 +674,4 @@ Find more information about all of SendGrid's whitelabeling realated doucmentati You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-csharp/blob/master/USAGE.md#stats). -Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email. \ No newline at end of file +Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email. diff --git a/src/SendGrid/Helpers/Mail/SendGridMessage.cs b/src/SendGrid/Helpers/Mail/SendGridMessage.cs index a6b0cab1f..63304496a 100644 --- a/src/SendGrid/Helpers/Mail/SendGridMessage.cs +++ b/src/SendGrid/Helpers/Mail/SendGridMessage.cs @@ -5,11 +5,14 @@ namespace SendGrid.Helpers.Mail { - using Model; - using Newtonsoft.Json; using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Model; + using Newtonsoft.Json; /// /// Class SendGridMessage builds an object that sends an email through SendGrid. @@ -984,57 +987,87 @@ public void AddContents(List contents) return; } + /// + /// Add an attachment from a stream to the email. No attachment will be added in the case that the stream cannot be read. Streams of length greater than int.MaxValue are truncated. + /// + /// The filename the attachment will display in the email. + /// The stream to use as content of the attachment. + /// The mime type of the content you are attaching. For example, application/pdf or image/jpeg. + /// The content-disposition of the attachment specifying how you would like the attachment to be displayed. For example, "inline" results in the attached file being displayed automatically within the message while "attachment" results in the attached file requiring some action to be taken before it is displayed (e.g. opening or downloading the file). Defaults to "attachment". Can be either "attachment" or "inline". + /// A unique id that you specify for the attachment. This is used when the disposition is set to "inline" and the attachment is an image, allowing the file to be displayed within the body of your email. Ex: ]]> + /// A cancellation token which can notify if the task should be canceled. + /// A representing the asynchronous operation. + public async Task AddAttachmentAsync(string filename, Stream contentStream, string type = null, string disposition = null, string content_id = null, CancellationToken cancellationToken = default(CancellationToken)) + { + // Stream doesn't want us to read it, can't do anything else here + if (contentStream == null || !contentStream.CanRead) + { + return; + } + + var contentLength = Convert.ToInt32(contentStream.Length); + var streamBytes = new byte[contentLength]; + + await contentStream.ReadAsync(streamBytes, 0, contentLength, cancellationToken); + + var base64Content = Convert.ToBase64String(streamBytes); + + this.AddAttachment(filename, base64Content, type, disposition, content_id); + } + /// /// Add an attachment to the email. /// - /// The filename of the attachment. - /// The Base64 encoded content of the attachment. + /// The filename the attachment will display in the email. + /// The Base64 encoded content of the attachment. /// The mime type of the content you are attaching. For example, application/pdf or image/jpeg. /// The content-disposition of the attachment specifying how you would like the attachment to be displayed. For example, "inline" results in the attached file being displayed automatically within the message while "attachment" results in the attached file requiring some action to be taken before it is displayed (e.g. opening or downloading the file). Defaults to "attachment". Can be either "attachment" or "inline". /// A unique id that you specify for the attachment. This is used when the disposition is set to "inline" and the attachment is an image, allowing the file to be displayed within the body of your email. Ex: ]]> - public void AddAttachment(string filename, string content, string type = null, string disposition = null, string content_id = null) + public void AddAttachment(string filename, string base64Content, string type = null, string disposition = null, string content_id = null) { - var attachment = new Attachment() + if (string.IsNullOrWhiteSpace(filename) || string.IsNullOrWhiteSpace(base64Content)) + { + return; + } + + var attachment = new Attachment { Filename = filename, - Content = content, + Content = base64Content, Type = type, Disposition = disposition, ContentId = content_id }; + this.AddAttachment(attachment); + } + + /// + /// Add an attachment to the email. + /// + /// An Attachment. + public void AddAttachment(Attachment attachment) + { if (this.Attachments == null) { - this.Attachments = new List() - { - attachment - }; - } - else - { - this.Attachments.Add(attachment); + this.Attachments = new List(); } - return; + this.Attachments.Add(attachment); } /// /// Add attachments to the email. /// /// A list of Attachments. - public void AddAttachments(List attachments) + public void AddAttachments(IEnumerable attachments) { if (this.Attachments == null) { this.Attachments = new List(); - this.Attachments = attachments; - } - else - { - this.Attachments.AddRange(attachments); } - return; + this.Attachments.AddRange(attachments); } /// diff --git a/tests/SendGrid.Tests/Helpers/Mail/NonReadableStream.cs b/tests/SendGrid.Tests/Helpers/Mail/NonReadableStream.cs new file mode 100644 index 000000000..d8aa47521 --- /dev/null +++ b/tests/SendGrid.Tests/Helpers/Mail/NonReadableStream.cs @@ -0,0 +1,43 @@ +using System.IO; + +namespace SendGrid.Tests.Helpers.Mail +{ + + public class NonReadableStream : Stream + { + public override bool CanRead => false; + + public override bool CanSeek => throw new System.NotImplementedException(); + + public override bool CanWrite => throw new System.NotImplementedException(); + + public override long Length => throw new System.NotImplementedException(); + + public override long Position { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + + public override void Flush() + { + throw new System.NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new System.NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new System.NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/tests/SendGrid.Tests/Helpers/Mail/SendGridMessageTests.cs b/tests/SendGrid.Tests/Helpers/Mail/SendGridMessageTests.cs new file mode 100644 index 000000000..c69809098 --- /dev/null +++ b/tests/SendGrid.Tests/Helpers/Mail/SendGridMessageTests.cs @@ -0,0 +1,285 @@ +namespace SendGrid.Tests.Helpers.Mail +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using SendGrid.Helpers.Mail; + using Xunit; + + public class SendGridMessageTests + { + #region AddAttachment tests + + [Theory] + [InlineData(null, "foo")] + [InlineData("", "foo")] + [InlineData(" ", "foo")] + [InlineData("foo", null)] + [InlineData("foo", "")] + [InlineData("foo", " ")] + public void SendGridMessage_AddAttachment_Doesnt_Add_If_Filename_Or_Content_Are_Missing(string filename, string content) + { + // Arrange + var sut = new SendGridMessage(); + + // Act + sut.AddAttachment(filename, content); + + // Assert + Assert.Null(sut.Attachments); + } + + [Theory] + [InlineData("filename", "content", null, null, null)] + [InlineData("filename", "content", "type", null, null)] + [InlineData("filename", "content", "type", "disposition", null)] + [InlineData("filename", "content", "type", "disposition", "contentId")] + public void SendGridMessage_AddAttachment_Should_Add_Single_Attachment_To_Attachments(string filename, string content, string type, string disposition, string contentId) + { + // Arrange + var sut = new SendGridMessage(); + + // Act + sut.AddAttachment(filename, content, type, disposition, contentId); + + // Assert + Assert.Equal(1, sut.Attachments.Count); + + var attachment = sut.Attachments.First(); + + Assert.Equal(filename, attachment.Filename); + Assert.Equal(content, attachment.Content); + Assert.Equal(type, attachment.Type); + Assert.Equal(disposition, attachment.Disposition); + Assert.Equal(contentId, attachment.ContentId); + } + + public void SendGridMessage_AddAttachment_Doesnt_Touch_Attachment_Passed_In() + { + // Arrange + var sut = new SendGridMessage(); + + var content = "content"; + var contentId = "contentId"; + var disposition = "disposition"; + var filename = "filename"; + var type = "type"; + + var attachment = new Attachment + { + Content = content, + ContentId = contentId, + Disposition = disposition, + Filename = filename, + Type = type + }; + + // Act + sut.AddAttachment(attachment); + + // Assert + Assert.Equal(1, sut.Attachments.Count); + + var addedAttachment = sut.Attachments.First(); + + Assert.Same(attachment, addedAttachment); + Assert.Equal(attachment.Content, addedAttachment.Content); + Assert.Equal(attachment.ContentId, addedAttachment.ContentId); + Assert.Equal(attachment.Disposition, addedAttachment.Disposition); + Assert.Equal(attachment.Filename, addedAttachment.Filename); + Assert.Equal(attachment.Type, addedAttachment.Type); + } + + #endregion + + #region AddAttachments tests + + [Fact] + public void SendGridMessage_AddAttachments_Adds_All_Attachments() + { + // Arrange + var sut = new SendGridMessage(); + + var attachments = new[] + { + new Attachment(), + new Attachment() + }; + + // Act + sut.AddAttachments(attachments); + + // Assert + Assert.Equal(attachments.Length, sut.Attachments.Count); + } + + [Fact] + public void SendGridMessage_AddAttachments_Doesnt_Use_Provided_List_As_Property() + { + // Arrange + var sut = new SendGridMessage(); + + var attachments = new List + { + new Attachment(), + new Attachment() + }; + + // Act + sut.AddAttachments(attachments); + + // Assert + Assert.Equal(attachments.Count(), sut.Attachments.Count); + Assert.NotSame(attachments, sut.Attachments); + } + + [Fact] + public void SendGridMessage_AddAttachments_Doesnt_Touch_Attachments_Passed_In() + { + // Arrange + var sut = new SendGridMessage(); + + var content = "content"; + var contentId = "contentId"; + var disposition = "disposition"; + var filename = "filename"; + var type = "type"; + + var attachment = new Attachment + { + Content = content, + ContentId = contentId, + Disposition = disposition, + Filename = filename, + Type = type + }; + + var attachments = new[] { attachment }; + + // Act + sut.AddAttachments(attachments); + + // Assert + Assert.Equal(1, sut.Attachments.Count); + + var addedAttachment = sut.Attachments.First(); + + Assert.Same(attachment, addedAttachment); + Assert.Equal(attachment.Content, addedAttachment.Content); + Assert.Equal(attachment.ContentId, addedAttachment.ContentId); + Assert.Equal(attachment.Disposition, addedAttachment.Disposition); + Assert.Equal(attachment.Filename, addedAttachment.Filename); + Assert.Equal(attachment.Type, addedAttachment.Type); + } + + #endregion + + #region AddAttachmentAsync tests + + [Fact] + public async Task SendGridMessage_AddAttachmentAsync_Doesnt_Read_Non_Readable_Streams() + { + // Arrange + var sut = new SendGridMessage(); + + var stream = new NonReadableStream(); + + // Act + await sut.AddAttachmentAsync(null, stream); + + // Assert + Assert.Null(sut.Attachments); + } + + [Fact] + public async Task SendGridMessage_AddAttachmentAsync_Adds_Base64_Content_Of_Stream() + { + // Arrange + var sut = new SendGridMessage(); + + var content = "hello world"; + var contentBytes = Encoding.UTF8.GetBytes(content); + var base64EncodedContent = Convert.ToBase64String(contentBytes); + var stream = new MemoryStream(contentBytes); + + // Act + await sut.AddAttachmentAsync("filename", stream); + + // Assert + Assert.Equal(1, sut.Attachments.Count); + + var attachment = sut.Attachments.First(); + + Assert.Equal(attachment.Content, base64EncodedContent); + } + + [Theory] + [InlineData(null, "foo")] + [InlineData("", "foo")] + [InlineData(" ", "foo")] + public async Task SendGridMessage_AddAttachmentAsync_Doesnt_Add_If_Filename_Is_Missing(string filename, string content) + { + // Arrange + var sut = new SendGridMessage(); + + var contentBytes = Encoding.UTF8.GetBytes(content); + var base64EncodedContent = Convert.ToBase64String(contentBytes); + var contentStream = new MemoryStream(contentBytes); + + // Act + await sut.AddAttachmentAsync(filename, contentStream); + + // Assert + Assert.Null(sut.Attachments); + } + + [Fact] + public async Task SendGridMessage_AddAttachmentAsync_Doesnt_Add_If_Content_Stream_Is_Missing() + { + // Arrange + var sut = new SendGridMessage(); + + Stream contentStream = null; + + // Act + await sut.AddAttachmentAsync("filename", contentStream); + + // Assert + Assert.Null(sut.Attachments); + } + + [Theory] + [InlineData("filename", "content", null, null, null)] + [InlineData("filename", "content", "type", null, null)] + [InlineData("filename", "content", "type", "disposition", null)] + [InlineData("filename", "content", "type", "disposition", "contentId")] + public async Task SendGridMessage_AddAttachmentAsync_Should_Add_Single_Attachment_To_Attachments(string filename, string content, string type, string disposition, string contentId) + { + // Arrange + var sut = new SendGridMessage(); + + var contentBytes = Encoding.UTF8.GetBytes(content); + var base64EncodedContent = Convert.ToBase64String(contentBytes); + var contentStream = new MemoryStream(contentBytes); + + // Act + await sut.AddAttachmentAsync(filename, contentStream, type, disposition, contentId); + + // Assert + Assert.Equal(1, sut.Attachments.Count); + + var addedAttachment = sut.Attachments.First(); + + Assert.Equal(base64EncodedContent, addedAttachment.Content); + Assert.Equal(contentId, addedAttachment.ContentId); + Assert.Equal(disposition, addedAttachment.Disposition); + Assert.Equal(filename, addedAttachment.Filename); + Assert.Equal(type, addedAttachment.Type); + } + + #endregion + } +} diff --git a/tests/SendGrid.Tests/Integration.cs b/tests/SendGrid.Tests/Integration.cs index 0791ed31d..31cb27957 100644 --- a/tests/SendGrid.Tests/Integration.cs +++ b/tests/SendGrid.Tests/Integration.cs @@ -1,18 +1,17 @@ namespace SendGrid.Tests { - using Helpers.Mail; - using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; - using System.Threading.Tasks; - using Xunit; using System.Threading; - using System.Text; - using Helpers.Reliability; + using System.Threading.Tasks; + using Newtonsoft.Json; using Reliability; + using SendGrid.Helpers.Mail; + using SendGrid.Helpers.Reliability; + using Xunit; using Xunit.Abstractions; public class IntegrationFixture : IDisposable diff --git a/tests/SendGrid.Tests/Reliability/ReliabilitySettingsTests.cs b/tests/SendGrid.Tests/Reliability/ReliabilitySettingsTests.cs index dad52ce78..5357b078f 100644 --- a/tests/SendGrid.Tests/Reliability/ReliabilitySettingsTests.cs +++ b/tests/SendGrid.Tests/Reliability/ReliabilitySettingsTests.cs @@ -1,8 +1,7 @@ namespace SendGrid.Tests.Reliability { using System; - - using Helpers.Reliability; + using SendGrid.Helpers.Reliability; using Xunit; public class ReliabilitySettingsTests