diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 5275651..c874e74 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -34,60 +34,6 @@ jobs: - name: Restore Packages run: dotnet restore "${{ env.solution-path }}" + - name: Build - run: dotnet build "${{ env.solution-path }}" --no-restore --configuration Release -p:version=${{ steps.gitversion.outputs.majorMinorPatch }} - - code-quality: - if: github.actor != 'dependabot[bot]' - runs-on: windows-latest - name: Analyze Code Quality - env: - solution-path: './src/NetCore Utilities Email.sln' - steps: - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: zulu - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Cache SonarCloud packages - uses: actions/cache@v3.3.2 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v3.3.2 - with: - path: .\.sonar\scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell - run: | - New-Item -Path .\.sonar\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - - - name: Install GitVersion - run: dotnet tool install --global GitVersion.Tool - - - name: Determine Version - id: gitversion - uses: gittools/actions/gitversion/execute@v0.10.2 - with: - useConfigFile: true - - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell - run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"IowaComputerGurus_netcore.utilities.email" /o:"iowacomputergurus-github" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - dotnet restore "${{ env.solution-path }}" - dotnet build "${{ env.solution-path }}" --no-restore --configuration Release -p:version=${{ steps.gitversion.outputs.majorMinorPatch }} - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + run: dotnet build "${{ env.solution-path }}" --no-restore --configuration Release -p:version=${{ steps.gitversion.outputs.majorMinorPatch }} \ No newline at end of file diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index f5f0033..8db2b69 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -5,61 +5,7 @@ on: tags: - 'v*' -jobs: - code-quality: - runs-on: windows-latest - name: Analyze Code Quality - env: - solution-path: './src/NetCore Utilities Email.sln' - steps: - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: zulu - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Cache SonarCloud packages - uses: actions/cache@v3.3.2 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v3.3.2 - with: - path: .\.sonar\scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell - run: | - New-Item -Path .\.sonar\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - - - name: Install GitVersion - run: dotnet tool install --global GitVersion.Tool - - - name: Determine Version - id: gitversion - uses: gittools/actions/gitversion/execute@v0.10.2 - with: - useConfigFile: true - - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell - run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"IowaComputerGurus_netcore.utilities.email" /o:"iowacomputergurus-github" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - dotnet restore "${{ env.solution-path }}" - dotnet build "${{ env.solution-path }}" --no-restore --configuration Release -p:version=${{ steps.gitversion.outputs.majorMinorPatch }} - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" - +jobs: build: runs-on: ubuntu-latest name: Build & Publish to NuGet diff --git a/GitVersion.yml b/GitVersion.yml index a59d151..8b402e5 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,5 +1,5 @@ mode: ContinuousDeployment -next-version: 5.1.4 +next-version: 7.0.0 branches: develop: regex: develop diff --git a/README.md b/README.md index f097463..f6746d4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ This is a base library to provide utilities for working with email in .NET 6. This project is used by more concrete implementations such as NetCore.Utilities.Email.Smtp. -## Usage +## Breaking Changes (Version 7.0) + +Starting with version 7.0, all `IEmailService` methods have been converted to asynchronous operations. ## Installation Standard installation via NuGet Package Manager diff --git a/src/NetCore.Utilities.Email/EmailTemplateFactory.cs b/src/NetCore.Utilities.Email/EmailTemplateFactory.cs index 448f777..f7a819a 100644 --- a/src/NetCore.Utilities.Email/EmailTemplateFactory.cs +++ b/src/NetCore.Utilities.Email/EmailTemplateFactory.cs @@ -4,72 +4,86 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -namespace ICG.NetCore.Utilities.Email +namespace ICG.NetCore.Utilities.Email; + +/// +/// Factory class for creating email content, using templates. +/// +public interface IEmailTemplateFactory { /// - /// Factory class for creating email content, using templates. + /// Creates HTML email content utilizing a template /// - public interface IEmailTemplateFactory + /// The desired subject of the email + /// The desired content of the email, HTML formatted. + /// + /// An optional preview textual email content element, replaced in the template for more advanced + /// control + /// + /// The name of the template to use, rather than the initial template. + /// The updated content wrapped in the template + /// Thrown for missing subject or content + /// Thrown if requesting a template that is not defined + /// Thrown if the defined template file does not exist + string BuildEmailContent(string subject, string content, string preview = "", string templateName = ""); +} + +/// +public class EmailTemplateFactory : IEmailTemplateFactory +{ + private readonly IHostEnvironment _hostingEnvironment; + private readonly IOptions _templateSettings; + + /// + /// Default constructor with injected dependencies + /// + /// + /// + public EmailTemplateFactory(IOptions templateSettings, IHostEnvironment hostingEnvironment) { - /// - /// Creates HTML email content utilizing a template - /// - /// The desired subject of the email - /// The desired content of the email, HTML formatted. - /// - /// An optional preview textual email content element, replaced in the template for more advanced - /// control - /// - /// The name of the template to use, rather than the initial template. - /// The updated content wrapped in the template - /// Thrown for missing subject or content - /// Thrown if requesting a template that is not defined - /// Thrown if the defined template file does not exist - string BuildEmailContent(string subject, string content, string preview = "", string templateName = ""); + _templateSettings = templateSettings; + _hostingEnvironment = hostingEnvironment; } - - public class EmailTemplateFactory : IEmailTemplateFactory + /// + public string BuildEmailContent(string subject, string content, string preview = "", string templateName = "") { - private readonly IHostEnvironment _hostingEnvironment; - private readonly IOptions _templateSettings; + //Validate inputs + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentNullException(nameof(subject)); + } - public EmailTemplateFactory(IOptions templateSettings, - IHostEnvironment hostingEnvironment) + if (string.IsNullOrEmpty(content)) { - _templateSettings = templateSettings; - _hostingEnvironment = hostingEnvironment; + throw new ArgumentNullException(nameof(content)); } - public string BuildEmailContent(string subject, string content, string preview = "", string templateName = "") + if (!string.IsNullOrEmpty(templateName) && + !_templateSettings.Value.AdditionalTemplates.ContainsKey(templateName)) { - //Validate inputs - if (string.IsNullOrEmpty(subject)) - throw new ArgumentNullException(nameof(subject)); - if (string.IsNullOrEmpty(content)) - throw new ArgumentNullException(nameof(content)); - if (!string.IsNullOrEmpty(templateName) && - !_templateSettings.Value.AdditionalTemplates.ContainsKey(templateName)) - throw new ArgumentException($"Requested template {templateName} was not found in configuration", - nameof(templateName)); + throw new ArgumentException($"Requested template {templateName} was not found in configuration", + nameof(templateName)); + } - //Get the template - var templatePath = string.IsNullOrEmpty(templateName) - ? _templateSettings.Value.DefaultTemplatePath - : _templateSettings.Value.AdditionalTemplates[templateName]; - var fullTemplatePath = Path.Combine(_hostingEnvironment.ContentRootPath, templatePath); - if (!File.Exists(fullTemplatePath)) - throw new FileNotFoundException("Unable to find template file", fullTemplatePath); + //Get the template + var templatePath = string.IsNullOrEmpty(templateName) + ? _templateSettings.Value.DefaultTemplatePath + : _templateSettings.Value.AdditionalTemplates[templateName]; + var fullTemplatePath = Path.Combine(_hostingEnvironment.ContentRootPath, templatePath); + if (!File.Exists(fullTemplatePath)) + { + throw new FileNotFoundException("Unable to find template file", fullTemplatePath); + } - //Replace the content - var templateBuilder = - new StringBuilder(File.ReadAllText(Path.Combine(_hostingEnvironment.ContentRootPath, templatePath))); - templateBuilder.Replace("[SUBJECT]", subject); - templateBuilder.Replace("[PREVIEW]", preview); - templateBuilder.Replace("[CONTENT]", content); + //Replace the content + var templateBuilder = + new StringBuilder(File.ReadAllText(fullTemplatePath)); + templateBuilder.Replace("[SUBJECT]", subject); + templateBuilder.Replace("[PREVIEW]", preview); + templateBuilder.Replace("[CONTENT]", content); - //Return message content - return templateBuilder.ToString(); - } + //Return message content + return templateBuilder.ToString(); } } \ No newline at end of file diff --git a/src/NetCore.Utilities.Email/EmailTemplateSettings.cs b/src/NetCore.Utilities.Email/EmailTemplateSettings.cs index a1f7a44..a5639d1 100644 --- a/src/NetCore.Utilities.Email/EmailTemplateSettings.cs +++ b/src/NetCore.Utilities.Email/EmailTemplateSettings.cs @@ -1,10 +1,19 @@ using System.Collections.Generic; -namespace ICG.NetCore.Utilities.Email +namespace ICG.NetCore.Utilities.Email; + +/// +/// Setting object for email templates +/// +public class EmailTemplateSettings { - public class EmailTemplateSettings - { - public string DefaultTemplatePath { get; set; } - public Dictionary AdditionalTemplates { get; set; } - } + /// + /// The application relative file path to the default template file + /// + public string DefaultTemplatePath { get; set; } + + /// + /// A collection of additional templates with a "name" and "path" for each one + /// + public Dictionary AdditionalTemplates { get; set; } } \ No newline at end of file diff --git a/src/NetCore.Utilities.Email/IEmailService.cs b/src/NetCore.Utilities.Email/IEmailService.cs index 5044dec..c953fe8 100644 --- a/src/NetCore.Utilities.Email/IEmailService.cs +++ b/src/NetCore.Utilities.Email/IEmailService.cs @@ -1,160 +1,169 @@ using System.Collections.Generic; +using System.Threading.Tasks; -namespace ICG.NetCore.Utilities.Email +namespace ICG.NetCore.Utilities.Email; + +/// +/// Represents a service that can deliver email messages to the recipients. This is utilized by all downstream +/// implementations of ICG.NetCore.Utilities.Email applications allowing easy switching between providers & options. +/// +public interface IEmailService { /// - /// Represents a service that can deliver email messages to the recipients. This is utilized by all downstream - /// implementations of ICG.NetCore.Utilities.Email applications allowing easy switching between providers & options. + /// Returns the configured administrator email for the service + /// + string AdminEmail { get; } + + /// + /// Returns the configured administrator name for outbound emails + /// + string AdminName { get; } + + /// + /// Shortcut for sending an email to the administrator, only requiring the subject and body. + /// + /// The message subject + /// The message body + Task SendMessageToAdministratorAsync(string subject, string bodyHtml); + + /// + /// Sends a message to the administrator as well as the additional contacts provided. + /// + /// Additional email addresses to add to the CC line + /// The email subject + /// The HTML content of the email + Task SendMessageToAdministratorAsync(IEnumerable ccAddressList, string subject, string bodyHtml); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// Who is receiving the email + /// The message subject + /// The message body + Task SendMessageAsync(string toAddress, string subject, string bodyHtml); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// Who is receiving the email + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + Task SendMessageAsync(string toAddress, string subject, string bodyHtml, + List> tokens); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + Task SendMessageAsync(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + Task SendMessageAsync(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, + List> tokens); + + /// + /// Sends a message to the specified recipient, and CC's with the supplied subject and body + /// + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + /// The optional custom template to override with + /// The a custom key for identifying a sender + Task SendMessageAsync(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, + List> tokens, + string templateName, string senderKeyName = ""); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// The address to be used as a reply to + /// The address to be used as a reply to + /// Who is receiving the email + /// The message subject + /// The message body + Task SendWithReplyToAsync(string replyToAddress, string replyToName, string toAddress, string subject, + string bodyHtml); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// The address to be used as a reply to + /// The address to be used as a reply to + /// Who is receiving the email + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + Task SendWithReplyToAsync(string replyToAddress, string replyToName, string toAddress, string subject, + string bodyHtml, List> tokens); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// The address to be used as a reply to + /// The address to be used as a reply to + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + Task SendWithReplyToAsync(string replyToAddress, string replyToName, string toAddress, + IEnumerable ccAddressList, string subject, string bodyHtml); + + /// + /// Sends a message to the specified recipient, with the supplied subject and body + /// + /// The address to be used as a reply to + /// The address to be used as a reply to + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + Task SendWithReplyToAsync(string replyToAddress, string replyToName, string toAddress, + IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens); + + /// + /// Sends a message to the specified recipient, and CC's with the supplied subject and body + /// + /// The address to be used as a reply to + /// The address to be used as a reply to + /// Who is receiving the email + /// Additional CC'ed emails + /// The message subject + /// The message body + /// A list of tokens that should be replaced within the email message + /// The optional custom template to override with + /// The a custom key for identifying a sender + Task SendWithReplyToAsync(string replyToAddress, string replyToName, string toAddress, + IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens, + string templateName, string senderKeyName = ""); + + /// + /// Creates a message with an attachment /// - public interface IEmailService - { - /// - /// Returns the configured administrator email for the service - /// - string AdminEmail { get; } - - /// - /// Returns the configured administrator name for outbound emails - /// - string AdminName { get; } - - /// - /// Shortcut for sending an email to the administrator, only requiring the subject and body. - /// - /// The message subject - /// The message body - bool SendMessageToAdministrator(string subject, string bodyHtml); - - /// - /// Sends a message to the administrator as well as the additional contacts provided. - /// - /// Additional email addresses to add to the CC line - /// The email subject - /// The HTML content of the email - bool SendMessageToAdministrator(IEnumerable ccAddressList, string subject, string bodyHtml); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// Who is receiving the email - /// The message subject - /// The message body - bool SendMessage(string toAddress, string subject, string bodyHtml); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// Who is receiving the email - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - bool SendMessage(string toAddress, string subject, string bodyHtml, List> tokens); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - bool SendMessage(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - bool SendMessage(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens); - - /// - /// Sends a message to the specified recipient, and CC's with the supplied subject and body - /// - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - /// The optional custom template to override with - /// The a custom key for identifying a sender - bool SendMessage(string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens, - string templateName, string senderKeyName = ""); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// The address to be used as a reply to - /// The address to be used as a reply to - /// Who is receiving the email - /// The message subject - /// The message body - bool SendWithReplyTo(string replyToAddress, string replyToName, string toAddress, string subject, string bodyHtml); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// The address to be used as a reply to - /// The address to be used as a reply to - /// Who is receiving the email - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - bool SendWithReplyTo(string replyToAddress, string replyToName, string toAddress, string subject, string bodyHtml, List> tokens); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// The address to be used as a reply to - /// The address to be used as a reply to - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - bool SendWithReplyTo(string replyToAddress, string replyToName, string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml); - - /// - /// Sends a message to the specified recipient, with the supplied subject and body - /// - /// The address to be used as a reply to - /// The address to be used as a reply to - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - bool SendWithReplyTo(string replyToAddress, string replyToName, string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens); - - /// - /// Sends a message to the specified recipient, and CC's with the supplied subject and body - /// - /// The address to be used as a reply to - /// The address to be used as a reply to - /// Who is receiving the email - /// Additional CC'ed emails - /// The message subject - /// The message body - /// A list of tokens that should be replaced within the email message - /// The optional custom template to override with - /// The a custom key for identifying a sender - bool SendWithReplyTo(string replyToAddress, string replyToName, string toAddress, IEnumerable ccAddressList, string subject, string bodyHtml, List> tokens, - string templateName, string senderKeyName = ""); - - /// - /// Creates a message with an attachment - /// - /// The to address for the message - /// The address(ses) to add a CC's - /// The subject of the message - /// Attachment Content - /// Attachment file name - /// The HTML body contents - /// A list of tokens that should be replaced within the email message - /// The optional custom template to override with - /// The a custom key for identifying a sender - /// - bool SendMessageWithAttachment(string toAddress, IEnumerable ccAddressList, string subject, - byte[] fileContent, string fileName, string bodyHtml, List> tokens, string templateName = "", string senderKeyName = ""); - } + /// The to address for the message + /// The address(ses) to add a CC's + /// The subject of the message + /// Attachment Content + /// Attachment file name + /// The HTML body contents + /// A list of tokens that should be replaced within the email message + /// The optional custom template to override with + /// The a custom key for identifying a sender + /// + Task SendMessageWithAttachmentAsync(string toAddress, IEnumerable ccAddressList, string subject, + byte[] fileContent, string fileName, string bodyHtml, List> tokens, + string templateName = "", string senderKeyName = ""); } \ No newline at end of file