From d9e2ac92911723d9a99b0a732806f4dba5f09e38 Mon Sep 17 00:00:00 2001 From: David De Sloovere Date: Fri, 7 Apr 2023 09:40:25 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=80=20chore:=20upgrade=20Microsoft.Gra?= =?UTF-8?q?ph=20to=20v5=20(#178)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update dependencies * version --- .../FluentEmail.Graph.csproj | 18 +- .../FluentEmailServicesBuilderExtensions.cs | 75 +++-- src/FluentEmail.Graph/GraphSender.cs | 301 +++++++++--------- src/FluentEmail.Graph/GraphSenderOptions.cs | 35 +- src/FluentEmail.Graph/MessageCreation.cs | 8 +- tests/FluentEmail.Graph.Tests/UnitTest1.cs | 35 +- 6 files changed, 243 insertions(+), 229 deletions(-) diff --git a/src/FluentEmail.Graph/FluentEmail.Graph.csproj b/src/FluentEmail.Graph/FluentEmail.Graph.csproj index 375f7c1..a5539af 100644 --- a/src/FluentEmail.Graph/FluentEmail.Graph.csproj +++ b/src/FluentEmail.Graph/FluentEmail.Graph.csproj @@ -20,9 +20,11 @@ 0.0.1 MIT detailed - 2.3 - v2.2 Added support for Headers -v2.1 Added support for Inline images + 2.4 + v2.4 Updated Microsoft.Graph to v5 +v2.2 Added support for Headers +v2.1 Added support for Inline images + @@ -38,16 +40,16 @@ v2.1 Added support for Inline images - + - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs b/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs index 3aa9538..afe9eb4 100644 --- a/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs +++ b/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs @@ -1,44 +1,43 @@ -namespace FluentEmail.Graph -{ - using FluentEmail.Core.Interfaces; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.DependencyInjection.Extensions; - using Microsoft.Graph; +namespace FluentEmail.Graph; + +using FluentEmail.Core.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Graph; - /// - /// Contains extension methods to register the with the FluentEmailServicesBuilder from FluentEmail.Core. - /// - public static class FluentEmailServicesBuilderExtensions +/// +/// Contains extension methods to register the with the FluentEmailServicesBuilder from FluentEmail.Core. +/// +public static class FluentEmailServicesBuilderExtensions +{ + public static FluentEmailServicesBuilder AddGraphSender( + this FluentEmailServicesBuilder builder, + GraphSenderOptions options) { - public static FluentEmailServicesBuilder AddGraphSender( - this FluentEmailServicesBuilder builder, - GraphSenderOptions options) - { - builder.Services.TryAdd(ServiceDescriptor.Scoped(_ => new GraphSender(options))); - return builder; - } + builder.Services.TryAdd(ServiceDescriptor.Scoped(_ => new GraphSender(options))); + return builder; + } - public static FluentEmailServicesBuilder AddGraphSender( - this FluentEmailServicesBuilder builder, - string graphEmailClientId, - string graphEmailTenantId, - string graphEmailSecret) + public static FluentEmailServicesBuilder AddGraphSender( + this FluentEmailServicesBuilder builder, + string graphEmailClientId, + string graphEmailTenantId, + string graphEmailSecret) + { + var options = new GraphSenderOptions { - var options = new GraphSenderOptions - { - ClientId = graphEmailClientId, - TenantId = graphEmailTenantId, - Secret = graphEmailSecret, - }; - return builder.AddGraphSender(options); - } + ClientId = graphEmailClientId, + TenantId = graphEmailTenantId, + Secret = graphEmailSecret, + }; + return builder.AddGraphSender(options); + } - public static FluentEmailServicesBuilder AddGraphSender( - this FluentEmailServicesBuilder builder, - GraphServiceClient graphClient) - { - builder.Services.TryAdd(ServiceDescriptor.Scoped(_ => new GraphSender(graphClient))); - return builder; - } + public static FluentEmailServicesBuilder AddGraphSender( + this FluentEmailServicesBuilder builder, + GraphServiceClient graphClient) + { + builder.Services.TryAdd(ServiceDescriptor.Scoped(_ => new GraphSender(graphClient))); + return builder; } -} +} \ No newline at end of file diff --git a/src/FluentEmail.Graph/GraphSender.cs b/src/FluentEmail.Graph/GraphSender.cs index 2881c31..0ea60ef 100644 --- a/src/FluentEmail.Graph/GraphSender.cs +++ b/src/FluentEmail.Graph/GraphSender.cs @@ -1,175 +1,190 @@ -namespace FluentEmail.Graph +namespace FluentEmail.Graph; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Azure.Identity; +using FluentEmail.Core; +using FluentEmail.Core.Interfaces; +using FluentEmail.Core.Models; +using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; +using Microsoft.Graph.Users.Item.Messages.Item.Attachments.CreateUploadSession; +using Microsoft.Graph.Users.Item.SendMail; +using Attachment = FluentEmail.Core.Models.Attachment; + +/// +/// Implementation of ISender for the Microsoft Graph API. +/// See . +/// +public class GraphSender : ISender { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Azure.Identity; - using FluentEmail.Core; - using FluentEmail.Core.Interfaces; - using FluentEmail.Core.Models; - using Microsoft.Graph; - - /// - /// Implementation of ISender for the Microsoft Graph API. - /// See . - /// - public class GraphSender : ISender - { - private const int ThreeMbLimit = 3145728; + private const int ThreeMbLimit = 3145728; - private readonly GraphServiceClient graphClient; + private readonly GraphServiceClient graphClient; - public GraphSender(GraphSenderOptions options) - { - ClientSecretCredential spn = new ( - options.TenantId, - options.ClientId, - options.Secret); - this.graphClient = new (spn); - } + public GraphSender(GraphSenderOptions options) + { + ClientSecretCredential spn = new ( + options.TenantId, + options.ClientId, + options.Secret); + this.graphClient = new (spn); + } - public GraphSender(GraphServiceClient graphClient) - { - this.graphClient = graphClient; - } + public GraphSender(GraphServiceClient graphClient) + { + this.graphClient = graphClient; + } - public SendResponse Send(IFluentEmail email, CancellationToken? token = null) - { - return this.SendAsync(email, token) - .GetAwaiter() - .GetResult(); - } + public SendResponse Send(IFluentEmail email, CancellationToken? token = default) + { + return this.SendAsync(email, token) + .GetAwaiter() + .GetResult(); + } - public async Task SendAsync(IFluentEmail email, CancellationToken? token = null) + public async Task SendAsync(IFluentEmail email, CancellationToken? token = default) + { + try { - try + token ??= default; + if (email.Data.Attachments?.Any() == true) { - if (email.Data.Attachments?.Any() == true) - { - var draftMessage = await this.SendWithAttachments(email); - - return new SendResponse { MessageId = draftMessage.Id, }; - } + var draftMessage = await this.SendWithAttachments(email, token.Value); - var message = await this.SendWithoutAttachments(email); - - return new SendResponse { MessageId = message.Id, }; - } - catch (Exception ex) - { - return new SendResponse { ErrorMessages = new List { ex.Message }, }; + return new SendResponse { MessageId = draftMessage.Id }; } - } - private static byte[] GetAttachmentBytes(Stream stream) - { - using var m = new MemoryStream(); - stream.CopyTo(m); + var message = await this.SendWithoutAttachments(email, token.Value); - return m.ToArray(); + // message.Id is empty + return new SendResponse { MessageId = message.Id }; } - - private async Task SendWithoutAttachments(IFluentEmail email) + catch (ODataError odataError) { - // https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http - var message = MessageCreation.CreateGraphMessageFromFluentEmail(email); - await this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .SendMail(message) - .Request() - .PostAsync(); - - return message; + return new SendResponse + { + ErrorMessages = new List { $"[{odataError.Error.Code}] {odataError.Error.Message}" }, + }; } - - private async Task SendWithAttachments(IFluentEmail email) + catch (Exception ex) { - // https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0&tabs=csharp - var draftMessage = await this.CreateDraftMessage(email); - await this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .Messages[draftMessage.Id] - .Send() - .Request() - .PostAsync(); - - return draftMessage; + return new SendResponse { ErrorMessages = new List { ex.Message } }; } + } - private async Task CreateDraftMessage(IFluentEmail email) - { - var template = MessageCreation.CreateGraphMessageFromFluentEmail(email); + private static byte[] GetAttachmentBytes(Stream stream) + { + using var m = new MemoryStream(); + stream.CopyTo(m); - var request = this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .Messages.Request(); + return m.ToArray(); + } - var draftMessage = await request.AddAsync(template); - foreach (var attachment in email.Data.Attachments) - { - if (attachment.Data.Length < ThreeMbLimit) - { - await this.UploadAttachmentUnder3Mb( - email, - draftMessage, - attachment); - } - else - { - await this.UploadAttachment3MbOrOver( - email, - draftMessage, - attachment); - } - } + private async Task SendWithoutAttachments( + IFluentEmail email, + CancellationToken cancellationToken = default) + { + // https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http + var message = MessageCreation.CreateGraphMessageFromFluentEmail(email); + await this.graphClient.Users[email.Data.FromAddress.EmailAddress] + .SendMail.PostAsync( + new SendMailPostRequestBody { Message = message }, + requestConfiguration: null, + cancellationToken: cancellationToken); + + return message; + } - return draftMessage; - } + private async Task SendWithAttachments(IFluentEmail email, CancellationToken cancellationToken) + { + // https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0&tabs=csharp + var message = MessageCreation.CreateGraphMessageFromFluentEmail(email); + + var draftMessage = await this.graphClient.Users[email.Data.FromAddress.EmailAddress] + .Messages.PostAsync(message, cancellationToken: cancellationToken); - private async Task UploadAttachmentUnder3Mb(IFluentEmail email, Message draft, Core.Models.Attachment a) + // upload attachments in the draft message + foreach (var attachment in email.Data.Attachments) { - var attachment = new FileAttachment + if (attachment.Data.Length < ThreeMbLimit) { - Name = a.Filename, - ContentType = a.ContentType, - ContentBytes = GetAttachmentBytes(a.Data), - ContentId = a.ContentId, - IsInline = a.IsInline, - - // can never be bigger than 3MB, so it is safe to cast to int - Size = (int)a.Data.Length, - }; + await this.UploadAttachmentUnder3Mb( + email, + draftMessage, + attachment, + cancellationToken); - await this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .Messages[draft.Id] - .Attachments.Request() - .AddAsync(attachment); + continue; + } + + await this.UploadAttachment3MbOrOver( + email, + draftMessage, + attachment, + cancellationToken); } - private async Task UploadAttachment3MbOrOver( - IFluentEmail email, - Message draft, - Core.Models.Attachment attachment) - { - var attachmentItem = new AttachmentItem - { - AttachmentType = AttachmentType.File, - Name = attachment.Filename, - Size = attachment.Data.Length, - ContentType = attachment.ContentType, - ContentId = attachment.ContentId, - IsInline = attachment.IsInline, - }; + await this.graphClient.Users[email.Data.FromAddress.EmailAddress] + .Messages[draftMessage.Id] + .Send.PostAsync(cancellationToken: cancellationToken); - var uploadSession = await this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .Messages[draft.Id] - .Attachments.CreateUploadSession(attachmentItem) - .Request() - .PostAsync(); + return draftMessage; + } - var fileUploadTask = new LargeFileUploadTask(uploadSession, attachment.Data); + private async Task UploadAttachmentUnder3Mb( + IFluentEmail email, + Message draft, + Attachment attachment, + CancellationToken cancellationToken) + { + var fileAttachment = new FileAttachment + { + Name = attachment.Filename, + ContentType = attachment.ContentType, + ContentBytes = GetAttachmentBytes(attachment.Data), + ContentId = attachment.ContentId, + IsInline = attachment.IsInline, + + // can never be bigger than 3MB, so it is safe to cast to int + Size = (int)attachment.Data.Length, + }; + + await this.graphClient.Users[email.Data.FromAddress.EmailAddress] + .Messages[draft.Id] + .Attachments.PostAsync(fileAttachment, cancellationToken: cancellationToken); + } - await fileUploadTask.UploadAsync(); - } + private async Task UploadAttachment3MbOrOver( + IFluentEmail email, + Message draft, + Attachment attachment, + CancellationToken cancellationToken) + { + var attachmentItem = new AttachmentItem + { + AttachmentType = AttachmentType.File, + Name = attachment.Filename, + Size = attachment.Data.Length, + ContentType = attachment.ContentType, + ContentId = attachment.ContentId, + IsInline = attachment.IsInline, + }; + + var uploadSession = await this.graphClient.Users[email.Data.FromAddress.EmailAddress] + .Messages[draft.Id] + .Attachments.CreateUploadSession.PostAsync( + new CreateUploadSessionPostRequestBody { AttachmentItem = attachmentItem }, + requestConfiguration: null, + cancellationToken: cancellationToken); + + var fileUploadTask = new LargeFileUploadTask(uploadSession, attachment.Data); + + await fileUploadTask.UploadAsync(cancellationToken: cancellationToken); } } diff --git a/src/FluentEmail.Graph/GraphSenderOptions.cs b/src/FluentEmail.Graph/GraphSenderOptions.cs index c846e72..2fcf99b 100644 --- a/src/FluentEmail.Graph/GraphSenderOptions.cs +++ b/src/FluentEmail.Graph/GraphSenderOptions.cs @@ -1,23 +1,22 @@ -namespace FluentEmail.Graph +namespace FluentEmail.Graph; + +/// +/// Contains settings needed for the . +/// +public class GraphSenderOptions { /// - /// Contains settings needed for the . + /// Gets or sets the Client ID (also known as App ID) of the application as registered in the application registration portal. /// - public class GraphSenderOptions - { - /// - /// Gets or sets the Client ID (also known as App ID) of the application as registered in the application registration portal. - /// - public string ClientId { get; set; } + public string ClientId { get; set; } - /// - /// Gets or sets the Tenant Id of the organization from which the application will let users sign-in. This is classically a GUID or a domain name. - /// - public string TenantId { get; set; } + /// + /// Gets or sets the Tenant Id of the organization from which the application will let users sign-in. This is classically a GUID or a domain name. + /// + public string TenantId { get; set; } - /// - /// Gets or sets the secret string previously shared with AAD at application registration to prove the identity of the application (the client) requesting the tokens. - /// - public string Secret { get; set; } - } -} + /// + /// Gets or sets the secret string previously shared with AAD at application registration to prove the identity of the application (the client) requesting the tokens. + /// + public string Secret { get; set; } +} \ No newline at end of file diff --git a/src/FluentEmail.Graph/MessageCreation.cs b/src/FluentEmail.Graph/MessageCreation.cs index d4905ff..3a9ac85 100644 --- a/src/FluentEmail.Graph/MessageCreation.cs +++ b/src/FluentEmail.Graph/MessageCreation.cs @@ -6,7 +6,7 @@ using FluentEmail.Core; using FluentEmail.Core.Models; using JetBrains.Annotations; -using Microsoft.Graph; +using Microsoft.Graph.Models; internal static class MessageCreation { @@ -34,7 +34,7 @@ internal static Message CreateGraphMessageFromFluentEmail(IFluentEmail email) return message; } - private static IList CreateRecipientList(IEnumerable
addressList) + private static List CreateRecipientList(IEnumerable
addressList) { if (addressList == null) { @@ -54,7 +54,7 @@ private static Recipient ConvertToRecipient([NotNull] Address address) return new Recipient { - EmailAddress = new EmailAddress { Address = address.EmailAddress, Name = address.Name, }, + EmailAddress = new EmailAddress { Address = address.EmailAddress, Name = address.Name }, }; } @@ -92,7 +92,7 @@ private static void AddHeaders(IFluentEmail email, Message message) } var headers = email.Data.Headers - .Select(header => new InternetMessageHeader { Name = header.Key, Value = header.Value, }) + .Select(header => new InternetMessageHeader { Name = header.Key, Value = header.Value }) .ToList(); message.InternetMessageHeaders = headers; } diff --git a/tests/FluentEmail.Graph.Tests/UnitTest1.cs b/tests/FluentEmail.Graph.Tests/UnitTest1.cs index 82b0d99..50713cd 100644 --- a/tests/FluentEmail.Graph.Tests/UnitTest1.cs +++ b/tests/FluentEmail.Graph.Tests/UnitTest1.cs @@ -1,23 +1,22 @@ -namespace FluentEmail.Graph.Tests -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace FluentEmail.Graph.Tests; + +using Microsoft.VisualStudio.TestTools.UnitTesting; - [TestClass] - public class UnitTest1 +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() { - [TestMethod] - public void TestMethod1() - { - // no tests yet, bit hard because you need actual appid, tenantid and secret for integration test - ////// arrange - ////const int Expected = 4; - ////var calculator = new Class1(); + // no tests yet, bit hard because you need actual appid, tenantid and secret for integration test + ////// arrange + ////const int Expected = 4; + ////var calculator = new Class1(); - ////// act - ////var actual = calculator.Add(2, 2); + ////// act + ////var actual = calculator.Add(2, 2); - ////// assert - ////actual.Should().Be(Expected); - } + ////// assert + ////actual.Should().Be(Expected); } -} +} \ No newline at end of file