diff --git a/Correo.sln b/Correo.sln
index 29e20eb..9e256cb 100644
--- a/Correo.sln
+++ b/Correo.sln
@@ -24,6 +24,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.PublishedLanguage",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.Domain", "src\Correo.Domain\Correo.Domain.csproj", "{A2D2694C-AB68-49B0-B4B0-94BBFF48C043}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.SmtpClient", "src\Correo.SmtpClient\Correo.SmtpClient.csproj", "{76793477-8219-4FFB-AA08-3F2224EC4968}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Correo.Abstractions", "src\Correo.Abstractions\Correo.Abstractions.csproj", "{ED8048DC-8509-4085-97F0-F9D9A59A689A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -46,6 +50,14 @@ Global
{A2D2694C-AB68-49B0-B4B0-94BBFF48C043}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2D2694C-AB68-49B0-B4B0-94BBFF48C043}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2D2694C-AB68-49B0-B4B0-94BBFF48C043}.Release|Any CPU.Build.0 = Release|Any CPU
+ {76793477-8219-4FFB-AA08-3F2224EC4968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {76793477-8219-4FFB-AA08-3F2224EC4968}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {76793477-8219-4FFB-AA08-3F2224EC4968}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {76793477-8219-4FFB-AA08-3F2224EC4968}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED8048DC-8509-4085-97F0-F9D9A59A689A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED8048DC-8509-4085-97F0-F9D9A59A689A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED8048DC-8509-4085-97F0-F9D9A59A689A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED8048DC-8509-4085-97F0-F9D9A59A689A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -55,6 +67,8 @@ Global
{3AEA1AB4-F068-4C39-A173-AD65F3F8E2F2} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
{DA47BCEE-9AE7-4F20-A04F-5866966503C5} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
{A2D2694C-AB68-49B0-B4B0-94BBFF48C043} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
+ {76793477-8219-4FFB-AA08-3F2224EC4968} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
+ {ED8048DC-8509-4085-97F0-F9D9A59A689A} = {245E2FBE-DFDF-40B4-94B7-5DDA216E58AD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {86FCF989-26FC-41E9-8A23-9485606D619D}
diff --git a/src/Correo.Abstractions/Correo.Abstractions.csproj b/src/Correo.Abstractions/Correo.Abstractions.csproj
new file mode 100644
index 0000000..dbc1517
--- /dev/null
+++ b/src/Correo.Abstractions/Correo.Abstractions.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net6.0
+
+
+
diff --git a/src/Correo.Domain/Models/EmailMessage.cs b/src/Correo.Abstractions/EmailMessage.cs
similarity index 94%
rename from src/Correo.Domain/Models/EmailMessage.cs
rename to src/Correo.Abstractions/EmailMessage.cs
index aaf4665..e70d49a 100644
--- a/src/Correo.Domain/Models/EmailMessage.cs
+++ b/src/Correo.Abstractions/EmailMessage.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace Correo.Domain.Models
+namespace Correo.Abstractions
{
public record EmailMessage
{
diff --git a/src/Correo.Abstractions/IMailer.cs b/src/Correo.Abstractions/IMailer.cs
new file mode 100644
index 0000000..194f854
--- /dev/null
+++ b/src/Correo.Abstractions/IMailer.cs
@@ -0,0 +1,11 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Correo.Abstractions
+{
+ public interface IMailer
+ {
+ void SendEmail(EmailMessage message);
+ Task SendEmailAsync(EmailMessage message, CancellationToken token = default);
+ }
+}
diff --git a/src/Correo.Application/CommandHandlers/SendEmailHandler.cs b/src/Correo.Application/CommandHandlers/SendEmailHandler.cs
index 5edf359..862f4ea 100644
--- a/src/Correo.Application/CommandHandlers/SendEmailHandler.cs
+++ b/src/Correo.Application/CommandHandlers/SendEmailHandler.cs
@@ -1,4 +1,5 @@
using AutoMapper;
+using Correo.Abstractions;
using Correo.Application.Extensions;
using Correo.Domain.Models;
using Correo.PublishedLanguage.Commands;
@@ -16,16 +17,18 @@ namespace Correo.Application.CommandHandlers
public class SendEmailHandler : IRequestHandler
{
private readonly IMessageBusPublisher _messageBusPublisher;
+ private readonly IOptions _defaultSender;
private readonly ILogger _logger;
private readonly IMapper _mapper;
- private readonly IOptions _defaultSender;
+ private readonly IMailer _mailer;
- public SendEmailHandler(IMessageBusPublisher messageBusPublisher, ILogger logger, IMapper mapper, IOptions defaultSender)
+ public SendEmailHandler(IMessageBusPublisher messageBusPublisher, IOptions defaultSender, ILogger logger, IMapper mapper, IMailer mailer)
{
_messageBusPublisher=messageBusPublisher;
+ _defaultSender=defaultSender;
_logger=logger;
_mapper=mapper;
- _defaultSender=defaultSender;
+ _mailer=mailer;
}
public async Task Handle(SendEmail command, CancellationToken cancellationToken)
@@ -33,15 +36,14 @@ namespace Correo.Application.CommandHandlers
try
{
var emailMessage = _mapper.Map(command);
- if (emailMessage.From == null)
- emailMessage.From = new EmailMessage.MailAddress(_defaultSender.Value.Address, _defaultSender.Value.Name);
-
+ emailMessage.Enrich(_defaultSender.Value);
emailMessage.Validate();
-
- // send email
+ await _mailer.SendEmailAsync(emailMessage, cancellationToken);
+ await _messageBusPublisher.PublishAsync(new EmailSent(command.Subject), cancellationToken);
}
catch (Exception ex)
{
+ _logger.LogError(ex, ex.Message);
await _messageBusPublisher.PublishAsync(new EmailSentFailed(command.Subject, ex.Message), cancellationToken);
throw;
}
diff --git a/src/Correo.Application/CommandHandlers/SendEmailHandlerSync.cs b/src/Correo.Application/CommandHandlers/SendEmailHandlerSync.cs
new file mode 100644
index 0000000..6d7441b
--- /dev/null
+++ b/src/Correo.Application/CommandHandlers/SendEmailHandlerSync.cs
@@ -0,0 +1,37 @@
+using Correo.Abstractions;
+using Correo.Application.Extensions;
+using Correo.Domain.Models;
+using MediatR;
+using Microsoft.Extensions.Options;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Correo.Application.CommandHandlers
+{
+ public class SendEmailHandlerSync
+ {
+ public record Command : EmailMessage, IRequest
+ {
+ }
+
+ public class CommandHandler : IRequestHandler
+ {
+ private readonly IOptions _defaultSender;
+ private readonly IMailer _mailer;
+
+ public CommandHandler(IOptions defaultSender, IMailer mailer)
+ {
+ _defaultSender=defaultSender;
+ _mailer=mailer;
+ }
+
+ public async Task Handle(Command command, CancellationToken cancellationToken)
+ {
+ command.Enrich(_defaultSender.Value);
+ command.Validate();
+ await _mailer.SendEmailAsync(command, cancellationToken);
+ return Unit.Value;
+ }
+ }
+ }
+}
diff --git a/src/Correo.Application/Correo.Application.csproj b/src/Correo.Application/Correo.Application.csproj
index 1e842d8..882d546 100644
--- a/src/Correo.Application/Correo.Application.csproj
+++ b/src/Correo.Application/Correo.Application.csproj
@@ -11,8 +11,10 @@
+
+
diff --git a/src/Correo.Application/DependencyInjectionExtensions.cs b/src/Correo.Application/DependencyInjectionExtensions.cs
index 0fd3631..2a73b8a 100644
--- a/src/Correo.Application/DependencyInjectionExtensions.cs
+++ b/src/Correo.Application/DependencyInjectionExtensions.cs
@@ -1,4 +1,5 @@
using Correo.Domain.Models;
+using Correo.SmtpClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -9,6 +10,7 @@ namespace Correo.Application
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure(configuration.GetSection("DefaultSender"));
+ services.AddSmtpClientServices(configuration.GetSection("SmtpClient"));
}
}
}
diff --git a/src/Correo.Application/Extensions/ModelExtensions.cs b/src/Correo.Application/Extensions/ModelExtensions.cs
new file mode 100644
index 0000000..023bd2d
--- /dev/null
+++ b/src/Correo.Application/Extensions/ModelExtensions.cs
@@ -0,0 +1,14 @@
+using Correo.Abstractions;
+using Correo.Domain.Models;
+
+namespace Correo.Application.Extensions
+{
+ internal static class ModelExtensions
+ {
+ public static void Enrich(this EmailMessage emailMessage, DefaultSender defaultSender)
+ {
+ if (emailMessage.From == null)
+ emailMessage.From = new EmailMessage.MailAddress(defaultSender.Address, defaultSender.Name);
+ }
+ }
+}
diff --git a/src/Correo.Application/Extensions/ValidationExtensions.cs b/src/Correo.Application/Extensions/ValidationExtensions.cs
index cc05b92..fc921f7 100644
--- a/src/Correo.Application/Extensions/ValidationExtensions.cs
+++ b/src/Correo.Application/Extensions/ValidationExtensions.cs
@@ -1,5 +1,5 @@
-using Correo.Application.Utils;
-using Correo.Domain.Models;
+using Correo.Abstractions;
+using Correo.Application.Utils;
using System;
using System.Linq;
diff --git a/src/Correo.Application/Mappings/MappingProfile.cs b/src/Correo.Application/Mappings/MappingProfile.cs
index 32b3305..728f11c 100644
--- a/src/Correo.Application/Mappings/MappingProfile.cs
+++ b/src/Correo.Application/Mappings/MappingProfile.cs
@@ -1,5 +1,6 @@
using AutoMapper;
-using Correo.Domain.Models;
+using Correo.Abstractions;
+using Correo.Application.CommandHandlers;
using Correo.PublishedLanguage.Commands;
namespace Correo.Application.Mappings
@@ -10,6 +11,7 @@ namespace Correo.Application.Mappings
{
CreateMap();
CreateMap();
+ CreateMap();
}
}
}
diff --git a/src/Correo.Application/Queries/GetSystemVersion.cs b/src/Correo.Application/Queries/GetSystemVersion.cs
index dc70921..d26a8b0 100644
--- a/src/Correo.Application/Queries/GetSystemVersion.cs
+++ b/src/Correo.Application/Queries/GetSystemVersion.cs
@@ -1,5 +1,7 @@
using MediatR;
using System;
+using System.IO;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -25,10 +27,22 @@ namespace Correo.Application.Queries
public async Task Handle(Query request, CancellationToken cancellationToken)
{
+ var version = Environment.GetEnvironmentVariable("APP_VERSION");
+ var appDate = Environment.GetEnvironmentVariable("APP_DATE");
+
+ if (string.IsNullOrEmpty(version))
+ version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion;
+
+ if (!DateTime.TryParse(appDate, out var lastUpdateDate))
+ {
+ var location = Assembly.GetExecutingAssembly().Location;
+ lastUpdateDate = File.GetLastWriteTime(location);
+ }
+
var result = new Model()
{
- Version = "1.0.0",
- LastUpdateDate = DateTime.Now
+ Version = version,
+ LastUpdateDate = lastUpdateDate
};
return await Task.FromResult(result);
diff --git a/src/Correo.Domain/Models/Settings.cs b/src/Correo.Domain/Models/Settings.cs
index b479757..b3af19d 100644
--- a/src/Correo.Domain/Models/Settings.cs
+++ b/src/Correo.Domain/Models/Settings.cs
@@ -1,4 +1,8 @@
namespace Correo.Domain.Models
{
- public record DefaultSender(string Address, string Name);
+ public record DefaultSender
+ {
+ public string Address { get; init; }
+ public string Name { get; init; }
+ }
}
diff --git a/src/Correo.SmtpClient/Correo.SmtpClient.csproj b/src/Correo.SmtpClient/Correo.SmtpClient.csproj
new file mode 100644
index 0000000..cc62f91
--- /dev/null
+++ b/src/Correo.SmtpClient/Correo.SmtpClient.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Correo.SmtpClient/DependencyInjectionExtensions.cs b/src/Correo.SmtpClient/DependencyInjectionExtensions.cs
new file mode 100644
index 0000000..c65fbc4
--- /dev/null
+++ b/src/Correo.SmtpClient/DependencyInjectionExtensions.cs
@@ -0,0 +1,16 @@
+using Correo.Abstractions;
+using Correo.SmtpClient.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Correo.SmtpClient
+{
+ public static class DependencyInjectionExtensions
+ {
+ public static void AddSmtpClientServices(this IServiceCollection services, IConfigurationSection configuration)
+ {
+ services.Configure(configuration);
+ services.AddTransient();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Correo.SmtpClient/Extensions/ModelExtensions.cs b/src/Correo.SmtpClient/Extensions/ModelExtensions.cs
new file mode 100644
index 0000000..628394a
--- /dev/null
+++ b/src/Correo.SmtpClient/Extensions/ModelExtensions.cs
@@ -0,0 +1,39 @@
+using Correo.Abstractions;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Mail;
+
+namespace Correo.SmtpClient.Extensions
+{
+ internal static class ModelExtensions
+ {
+ public static string Log(this IEnumerable addresses)
+ => addresses != null ? string.Join(',', addresses.Select(z => z.Address)) : string.Empty;
+
+ public static MailAddress ToMailAddress(this EmailMessage.MailAddress address)
+ => new MailAddress(address.Address, address.DisplayName);
+
+ public static void AddRange(this MailAddressCollection collection, IEnumerable addresses)
+ {
+ foreach (var item in addresses)
+ collection.Add(item.ToMailAddress());
+ }
+
+ public static MailMessage ToMailMessage(this EmailMessage message)
+ {
+ var mailMessage = new MailMessage
+ {
+ Subject = message.Subject,
+ Body = message.Body,
+ From = message.From.ToMailAddress(),
+ IsBodyHtml = message.IsBodyHtml
+ };
+
+ mailMessage.To.AddRange(message.To);
+ mailMessage.CC.AddRange(message.Cc);
+ mailMessage.Bcc.AddRange(message.Bcc);
+
+ return mailMessage;
+ }
+ }
+}
diff --git a/src/Correo.SmtpClient/Models/SmtpClientOptions.cs b/src/Correo.SmtpClient/Models/SmtpClientOptions.cs
new file mode 100644
index 0000000..dc43aa3
--- /dev/null
+++ b/src/Correo.SmtpClient/Models/SmtpClientOptions.cs
@@ -0,0 +1,19 @@
+namespace Correo.SmtpClient.Models
+{
+ public record SmtpClientOptions
+ {
+ public string Server { get; init; }
+ public int Port { get; init; }
+ public bool UseSsl { get; init; }
+ public bool UseAuthentication { get; init; }
+ public bool TrustServer { get; init; }
+ public AuthenticationOptions Authentication { get; init; }
+
+ public record AuthenticationOptions
+ {
+ public string UserName { get; init; }
+ public string Domain { get; init; }
+ public string Password { get; init; }
+ }
+ }
+}
diff --git a/src/Correo.SmtpClient/SmtpClientMailer.cs b/src/Correo.SmtpClient/SmtpClientMailer.cs
new file mode 100644
index 0000000..3e069db
--- /dev/null
+++ b/src/Correo.SmtpClient/SmtpClientMailer.cs
@@ -0,0 +1,64 @@
+using Correo.Abstractions;
+using Correo.SmtpClient.Extensions;
+using Correo.SmtpClient.Models;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Correo.SmtpClient
+{
+ public class SmtpClientMailer : IMailer, IDisposable
+ {
+ private readonly IOptions _optionsAccessor;
+ private readonly System.Net.Mail.SmtpClient _smtpClient;
+ private readonly ILogger _logger;
+
+ public SmtpClientMailer(IOptions optionsAccessor, ILogger logger)
+ {
+ _optionsAccessor = optionsAccessor;
+ _logger = logger;
+
+ var options = _optionsAccessor.Value;
+
+ _smtpClient = new System.Net.Mail.SmtpClient { Host = options.Server, Port = options.Port, EnableSsl = options.UseSsl };
+ if (options.UseAuthentication)
+ {
+ if (string.IsNullOrEmpty(options.Authentication.Domain))
+ _smtpClient.Credentials = new NetworkCredential(options.Authentication.UserName, options.Authentication.Password);
+ else
+ _smtpClient.Credentials = new NetworkCredential(options.Authentication.UserName, options.Authentication.Password, options.Authentication.Domain);
+ }
+ else
+ _smtpClient.UseDefaultCredentials = true;
+
+ if (options.TrustServer)
+ ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
+ }
+
+ public void Dispose()
+ {
+ _smtpClient?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ public void SendEmail(EmailMessage message)
+ {
+ var mailMessage = message.ToMailMessage();
+ _smtpClient.Send(mailMessage);
+ Log(message);
+ }
+
+ public async Task SendEmailAsync(EmailMessage message, CancellationToken token = default)
+ {
+ var mailMessage = message.ToMailMessage();
+ await _smtpClient.SendMailAsync(mailMessage, token);
+ Log(message);
+ }
+
+ private void Log(EmailMessage message)
+ => _logger.LogInformation($"Email sent: Subject: {message.Subject}; From: {message.From.Address}; To: {message.To.Log()}; Cc: {message.Cc.Log()}; Bcc: {message.Bcc.Log()};");
+ }
+}
diff --git a/src/Correo/Controllers/MailerController.cs b/src/Correo/Controllers/MailerController.cs
new file mode 100644
index 0000000..a5a4130
--- /dev/null
+++ b/src/Correo/Controllers/MailerController.cs
@@ -0,0 +1,31 @@
+using AutoMapper;
+using Correo.Application.CommandHandlers;
+using Correo.PublishedLanguage.Commands;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+
+namespace Correo.Controllers
+{
+ [ApiController]
+ [Route("api/mailer")]
+ public class MailerController : ControllerBase
+ {
+ private readonly IMediator _mediator;
+ private readonly IMapper _mapper;
+
+ public MailerController(IMediator mediator, IMapper mapper)
+ {
+ _mediator=mediator;
+ _mapper=mapper;
+ }
+
+ [HttpPost("email")]
+ public async Task SendEmail([FromBody] SendEmail sendEmail)
+ {
+ var command = _mapper.Map(sendEmail);
+ var result = await _mediator.Send(command);
+ return Ok(result);
+ }
+ }
+}
diff --git a/src/Correo/Controllers/SystemController.cs b/src/Correo/Controllers/SystemController.cs
index 84e8f23..555d259 100644
--- a/src/Correo/Controllers/SystemController.cs
+++ b/src/Correo/Controllers/SystemController.cs
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Correo.Controllers
{
[ApiController]
- [Route("[controller]")]
+ [Route("api/system")]
public class SystemController : ControllerBase
{
private readonly IMediator _mediator;
diff --git a/src/Correo/Extensions/StartupExtensions.cs b/src/Correo/Extensions/StartupExtensions.cs
index 7978814..ab29ef6 100644
--- a/src/Correo/Extensions/StartupExtensions.cs
+++ b/src/Correo/Extensions/StartupExtensions.cs
@@ -1,4 +1,5 @@
-using MediatR;
+using Correo.Application;
+using MediatR;
using MediatR.Pipeline;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
@@ -27,6 +28,9 @@ namespace Correo.Extensions
// Messaging
services.AddMessaging(configuration);
+
+ // Application services
+ services.AddApplicationServices(configuration);
}
public static void Configure(this WebApplication app)