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 y ILogger<MailService>) se inyectan en MailService durante la construcción del objeto, lo que permite que MailService 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:

  1. Configuración de la Clase de Prueba:

    • Mock<IConfiguration> y Mock<ILogger<MailService>>: Estas son instancias simuladas de IConfiguration y ILogger<MailService> creadas usando la biblioteca Moq. IConfiguration es utilizado para acceder a la configuración de la aplicación, y ILogger<MailService> se utiliza para registrar mensajes de log en MailService.

    • Simulación de la Configuración: Se configura IConfiguration para devolver valores simulados de la configuración SMTP. Esto se logra creando un Mock<IConfigurationSection> y configurándolo para devolver un objeto SmtpSettings con los valores deseados.

    • Inicialización de MailService: Finalmente, MailService se inicializa con las instancias simuladas de IConfiguration y ILogger<MailService>.

  2. Prueba SendEmail_ShouldSendEmail_WhenParametersAreValid:

    • Arrange: Se configuran los parámetros de prueba (to, subject, body) que se usarán para llamar al método SendEmail.

    • Act: Se llama al método SendEmail y se captura cualquier excepción que pueda ocurrir usando Record.Exception(). Esta línea de código ejecuta el método y guarda cualquier excepción lanzada en la variable ex.

    • Assert: Verificamos que no haya ninguna excepción (Assert.Null(ex)). Esto asegura que el método SendEmail se ejecuta sin errores cuando se proporcionan parámetros válidos.

  3. 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 configurando IConfiguration para lanzar una excepción cuando se llama a GetSection("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, usamos Verify para asegurarnos de que el logger haya registrado el error una vez (Times.Once). Esto asegura que MailService 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:

  1. Ve a "Ver" -> "Explorador de Pruebas".
  2. 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.

 

   EtiquetasASP.NET Core DI-IoC SMTP .NET Core

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

  Comentarios

No hay comentarios para este Post.



Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto