Inyección de Dependencias en ASP.NET Core: Un cliente SMTP (II)
En este artículo, vamos a explorar en detalle las mejoras y funcionalidades adicionales que podemos agregar a nuestra aplicación ASP.NET Core con inyección de dependencias para el cliente de correo electrónico (SMTP) MailService
. Seguiremos los "Próximos Pasos" sugeridos en las conclusiones del artículo anterior Inyección de Dependencias en ASP.NET Core: Un cliente SMTP (I), centrándonos en ampliar cada área con ejemplos de código detallados.
1. Autenticación y Autorización
La autenticación y la autorización son aspectos críticos de la seguridad en una aplicación web. Vamos a profundizar en cómo implementar la autenticación basada en tokens JWT (JSON Web Tokens) y configurar políticas de autorización para restringir el acceso a recursos específicos en el contexto de nuestra aplicación MailService.
1. Instalar Paquetes: Agregaremos los paquetes necesarios para la autenticación JWT:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
2. Configurar Autenticación: En el método ConfigureServices
de Startup.cs
, agregaremos la configuración de la autenticación:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Aquí estamos configurando el esquema de autenticación JWT y especificando los parámetros de validación del token, incluyendo la validez del emisor, del destinatario, del tiempo de vida y de la clave de firma.
3. Configurar Autorización: En el método Configure
de Startup.cs
, configuraremos las políticas de autorización:
app.UseAuthorization();
Esto habilita la autorización en la canalización de solicitudes y permite que ASP.NET Core aplique las políticas de autorización en los endpoints de la aplicación.
4. Proteger Recursos: Utilizaremos atributos de autorización en los controladores y acciones que requieran autenticación:
[Authorize]
public class SecureController : Controller
{
// Métodos protegidos
}
Este atributo garantiza que solo los usuarios autenticados puedan acceder a los métodos del controlador SecureController
.
5. Generación y Gestión de Tokens: Implementaremos la lógica para generar y gestionar tokens JWT en nuestra aplicación, incluyendo la autenticación de usuarios y la emisión de tokens válidos.
public async Task<IActionResult> Login(LoginModel model)
{
// Lógica para autenticar al usuario
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
var authClaims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Issuer"],
expires: DateTime.UtcNow.AddHours(1),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
En este método, autenticamos al usuario y generamos un token JWT válido que puede ser utilizado para futuras solicitudes.
6. Validación de Tokens: En el método Configure
de Startup.cs
, podemos agregar middleware de validación de tokens JWT para garantizar que los tokens enviados por los clientes sean válidos antes de permitir el acceso a los recursos protegidos:
app.UseAuthentication();
Este middleware valida los tokens JWT recibidos en las solicitudes entrantes y establece el contexto de autenticación de la solicitud si el token es válido.
Nota: Para mas información acerca de cómo configurar la seguridad de tus aplicaciones ASP.NET Core con JSON Web Tokens, visita y lee detenidamente el siguiente artículo de este Blog: JSON Web Token - Seguridad en servicios Web API de .NET Core.
2. Configuración de SMTP Real
En lugar de simular el envío de correos electrónicos, configuraremos un servidor SMTP real y utilizaremos la biblioteca System.Net.Mail
para enviar correos electrónicos reales. Esto nos permitirá integrarnos con proveedores de correo electrónico externos como Gmail, Outlook, etc.
1. Configurar Parámetros SMTP: En el archivo appsettings.json
, configuraremos los parámetros SMTP reales:
"SmtpSettings": {
"Server": "smtp.example.com",
"Port": 587,
"Username": "your_username",
"Password": "your_password"
}
2. Enviar Correo Electrónico Real: En el método SendEmail
de MailService.cs
, utilizaremos la clase SmtpClient
para enviar correos electrónicos reales:
using System;
using System.Net;
using System.Net.Mail;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
public class MailService
{
private readonly IConfiguration _configuration;
private readonly ILogger<MailService> _logger;
public MailService(IConfiguration configuration, ILogger<MailService> logger)
{
_configuration = configuration;
_logger = logger;
}
public void SendEmail(string to, string subject, string body)
{
// Obtener la configuración SMTP del archivo appsettings.json
var smtpSettings = _configuration.GetSection("SmtpSettings").Get<SmtpSettings>();
try
{
// Crear el cliente SMTP
using (var client = new SmtpClient(smtpSettings.Server, smtpSettings.Port))
{
// Configurar credenciales
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(smtpSettings.Username, smtpSettings.Password);
client.EnableSsl = true;
// Crear el mensaje de correo electrónico
var message = new MailMessage(smtpSettings.Username, to, subject, body);
// Enviar el correo electrónico
client.Send(message);
}
}
catch (SmtpException ex)
{
// Capturar y manejar errores SMTP específicos
_logger.LogError(ex, "Error al enviar correo electrónico: {ErrorMessage}", ex.Message);
throw new Exception("Error al enviar correo electrónico. Consulte los registros para más detalles.");
}
catch (Exception ex)
{
// Capturar y manejar errores generales
_logger.LogError(ex, "Error inesperado al enviar correo electrónico: {ErrorMessage}", ex.Message);
throw new Exception("Error inesperado al enviar correo electrónico. Consulte los registros para más detalles.");
}
}
}
En este ejemplo, _logger
es una instancia de ILogger<MailService>
que se inyecta en el constructor de MailService
mediante la inyección de dependencias de ASP.NET Core. ILogger<T>
es una interfaz proporcionada por ASP.NET Core para registrar mensajes de registro. Se utiliza para registrar cualquier error que pueda ocurrir durante el envío de correos electrónicos.
IConfiguration
es otra interfaz proporcionada por ASP.NET Core que nos permite acceder a la configuración de la aplicación, incluyendo la configuración definida en el archivo appsettings.json
. Se utiliza para obtener la configuración SMTP necesaria para enviar correos electrónicos.
Nota: Estos servicios (
IConfiguration
yILogger<MailService>
) se inyectan enMailService
durante la construcción del objeto, lo que permite queMailService
acceda a ellos y los utilice en su lógica.
3. Pruebas Unitarias
Las pruebas unitarias son una parte esencial del desarrollo de software moderno. Permiten verificar que cada parte de la aplicación funciona correctamente de manera aislada. En esta sección, explicaremos cómo realizar pruebas unitarias para nuestro MailService
usando frameworks de pruebas comunes como xUnit
y Moq
.
1. Configurar el Proyecto de Pruebas:
Primero, creamos un proyecto de pruebas en nuestra solución. Si aún no tienes un proyecto de pruebas, puedes agregar uno en Visual Studio:
- Haz clic derecho en la solución y selecciona "Agregar" -> "Nuevo Proyecto".
- Selecciona "Pruebas Unitarias de xUnit" y configura el proyecto.
2. Agregar Dependencias:
Asegúrate de agregar las dependencias necesarias a tu proyecto de pruebas. Puedes hacerlo editando el archivo csproj
o usando el Administrador de Paquetes NuGet. Necesitarás xUnit
y Moq
.
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
3. Escribir Pruebas Unitarias:
Ahora, podemos escribir pruebas unitarias para MailService
. Crearemos una clase de prueba llamada MailServiceTests
.
using System;
using Xunit;
using Moq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Net.Mail;
public class MailServiceTests
{
private readonly Mock<IConfiguration> _mockConfiguration;
private readonly Mock<ILogger<MailService>> _mockLogger;
private readonly MailService _mailService;
public MailServiceTests()
{
_mockConfiguration = new Mock<IConfiguration>();
_mockLogger = new Mock<ILogger<MailService>>();
// Configurar valores simulados para IConfiguration
var smtpSettings = new SmtpSettings
{
Server = "smtp.example.com",
Port = 587,
Username = "test_user",
Password = "test_password"
};
var mockSection = new Mock<IConfigurationSection>();
mockSection.Setup(x => x.Get<SmtpSettings>()).Returns(smtpSettings);
_mockConfiguration.Setup(x => x.GetSection("SmtpSettings")).Returns(mockSection.Object);
_mailService = new MailService(_mockConfiguration.Object, _mockLogger.Object);
}
[Fact]
public void SendEmail_ShouldSendEmail_WhenParametersAreValid()
{
// Arrange
var to = "test@example.com";
var subject = "Test Subject";
var body = "Test Body";
// Act
Exception ex = Record.Exception(() => _mailService.SendEmail(to, subject, body));
// Assert
Assert.Null(ex);
}
[Fact]
public void SendEmail_ShouldLogError_WhenSmtpExceptionOccurs()
{
// Arrange
var to = "test@example.com";
var subject = "Test Subject";
var body = "Test Body";
// Configurar el cliente SMTP para lanzar una excepción
_mockConfiguration.Setup(x => x.GetSection("SmtpSettings"))
.Throws(new SmtpException("SMTP error"));
// Act
Exception ex = Record.Exception(() => _mailService.SendEmail(to, subject, body));
// Assert
Assert.NotNull(ex);
_mockLogger.Verify(
x => x.LogError(It.IsAny<SmtpException>(),
It.IsAny<string>(),
It.IsAny<string>()),
Times.Once);
}
}
Explicaciones de las Pruebas:
-
Configuración de la Clase de Prueba:
-
Mock<IConfiguration>
yMock<ILogger<MailService>>
: Estas son instancias simuladas deIConfiguration
yILogger<MailService>
creadas usando la bibliotecaMoq
.IConfiguration
es utilizado para acceder a la configuración de la aplicación, yILogger<MailService>
se utiliza para registrar mensajes de log enMailService
. -
Simulación de la Configuración: Se configura
IConfiguration
para devolver valores simulados de la configuración SMTP. Esto se logra creando unMock<IConfigurationSection>
y configurándolo para devolver un objetoSmtpSettings
con los valores deseados. -
Inicialización de
MailService
: Finalmente,MailService
se inicializa con las instancias simuladas deIConfiguration
yILogger<MailService>
.
-
-
Prueba
SendEmail_ShouldSendEmail_WhenParametersAreValid
:-
Arrange: Se configuran los parámetros de prueba (
to
,subject
,body
) que se usarán para llamar al métodoSendEmail
. -
Act: Se llama al método
SendEmail
y se captura cualquier excepción que pueda ocurrir usandoRecord.Exception()
. Esta línea de código ejecuta el método y guarda cualquier excepción lanzada en la variableex
. -
Assert: Verificamos que no haya ninguna excepción (
Assert.Null(ex)
). Esto asegura que el métodoSendEmail
se ejecuta sin errores cuando se proporcionan parámetros válidos.
-
-
Prueba
SendEmail_ShouldLogError_WhenSmtpExceptionOccurs
:-
Arrange: Configuramos el cliente SMTP para lanzar una
SmtpException
simulada al intentar obtener la sección de configuración SMTP. Esto se hace configurandoIConfiguration
para lanzar una excepción cuando se llama aGetSection("SmtpSettings")
. -
Act: Se llama al método
SendEmail
y se captura cualquier excepción que pueda ocurrir. -
Assert: Verificamos que ocurra una excepción (
Assert.NotNull(ex)
). Además, usamosVerify
para asegurarnos de que el logger haya registrado el error una vez (Times.Once
). Esto asegura queMailService
maneja correctamente los errores de SMTP y los registra usando el logger.
-
Ejecutar las Pruebas:
Para ejecutar las pruebas, puedes usar el Explorador de Pruebas en Visual Studio:
- Ve a "Ver" -> "Explorador de Pruebas".
- Haz clic en "Ejecutar Todo" para ejecutar todas las pruebas unitarias.
Conclusiones
La correcta configuración del servicio MailService
para manejar el envío de correos electrónicos mediante un servidor SMTP real, junto con pruebas unitarias exhaustivas y una sólida infraestructura de autenticación y autorización, garantiza que la aplicación ASP.NET Core sea segura, funcional y mantenible. Los ejemplos de código proporcionados sirven como una guía práctica para implementar y probar estas características en tu propia aplicación, asegurando un desarrollo eficiente y una operación confiable del servicio de correo electrónico.
Nuevo comentario
Comentarios
No hay comentarios para este Post.