Introduction:
In this article, we will build the Infrastructure Layer in a .NET project, focusing on two key functionalities:
- Exporting event data to a CSV file using the
CsvHelper
library. - Sending email notifications when new events are created using the SendGrid service.
By following Clean Architecture principles, we'll ensure that the application remains decoupled from the actual infrastructure implementations. Let’s begin by setting up the project structure and building the infrastructure layer step by step.
1. Setting Up the Infrastructure Layer
The Infrastructure Layer handles external system interactions such as:
- Sending emails via an external email service (e.g., SendGrid).
- Exporting data to CSV files for reporting or analytics.
To keep the system maintainable, we separate the contracts (interfaces) into the Application Layer and place the actual implementations into the Infrastructure Layer.
Step 1: Add the Infrastructure Layer to the Solution
- In your solution, right-click on the solution name and select Add > New Project.
- Choose Class Library as the project type and name it
GloboTicket.TicketManagement.Infrastructure
.
2. Implementing CSV Export Functionality
Let’s first implement the CSV export functionality using the CsvHelper
library.
Step 2.1: Define the CSV Export Contract in the Application Layer
- In your Application Layer, navigate to
Contracts/Infrastructure
. - Add a new interface
ICsvExporter.cs
:
namespace GloboTicket.TicketManagement.Application.Contracts.Infrastructure
{
public interface ICsvExporter
{
byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos);
}
}
Step 2.2: Implement the CsvExporter
in the Infrastructure Layer
Now, let’s implement the actual export logic in the Infrastructure Layer.
- In your Infrastructure project, create a folder named
FileExport
. - Add a class named
CsvExporter.cs
:
using CsvHelper;
using System.IO;
using System.Text;
using GloboTicket.TicketManagement.Application.Contracts.Infrastructure;
using GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport;
namespace GloboTicket.TicketManagement.Infrastructure.FileExport
{
public class CsvExporter : ICsvExporter
{
public byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos)
{
using var memoryStream = new MemoryStream();
using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8))
{
using var csvWriter = new CsvWriter(streamWriter);
csvWriter.WriteRecords(eventExportDtos); // Write event records to CSV
}
return memoryStream.ToArray(); // Return CSV as byte array
}
}
}
-
Purpose: This class converts a list of
EventExportDto
objects to a CSV format and returns the data as a byte array.
Step 2.3: Create the CSV Export Feature in the Application Layer
Next, we’ll create the handler that will be responsible for exporting events.
- In the Application Layer, navigate to
Features/Events/Queries/GetEventsExport
.
- Add the following files step by step:
EventExportDto.cs
:
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{
public class EventExportDto
{
public Guid EventId { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime Date { get; set; }
}
}
EventExportFileVm.cs
:
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{
public class EventExportFileVm
{
public string EventExportFileName { get; set; } = string.Empty;
public string ContentType { get; set; } = string.Empty;
public byte[]? Data { get; set; }
}
}
GetEventsExportQuery.cs
:
using MediatR;
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{
public class GetEventsExportQuery : IRequest<EventExportFileVm>
{
}
}
GetEventsExportQueryHandler.cs
:
using MediatR;
using AutoMapper;
using GloboTicket.TicketManagement.Application.Contracts.Infrastructure;
using GloboTicket.TicketManagement.Domain.Entities;
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsExport
{
public class GetEventsExportQueryHandler : IRequestHandler<GetEventsExportQuery, EventExportFileVm>
{
private readonly IAsyncRepository<Event> _eventRepository;
private readonly IMapper _mapper;
private readonly ICsvExporter _csvExporter;
public GetEventsExportQueryHandler(IMapper mapper, IAsyncRepository<Event> eventRepository, ICsvExporter csvExporter)
{
_mapper = mapper;
_eventRepository = eventRepository;
_csvExporter = csvExporter;
}
public async Task<EventExportFileVm> Handle(GetEventsExportQuery request, CancellationToken cancellationToken)
{
var allEvents = _mapper.Map<List<EventExportDto>>((await _eventRepository.ListAllAsync()).OrderBy(x => x.Date));
var fileData = _csvExporter.ExportEventsToCsv(allEvents);
return new EventExportFileVm
{
ContentType = "text/csv",
Data = fileData,
EventExportFileName = $"{Guid.NewGuid()}.csv"
};
}
}
}
3. Implementing Email Functionality Using SendGrid
Next, let’s add email functionality using SendGrid.
Step 3.1: Define the Email Service Contract
- In the Application Layer, navigate to
Contracts/Infrastructure
. - Add a new interface
IEmailService.cs
:
namespace GloboTicket.TicketManagement.Application.Contracts.Infrastructure
{
public interface IEmailService
{
Task<bool> SendEmail(Email email);
}
}
Step 3.2: Implement the Email Service in the Infrastructure Layer
- In the Infrastructure project, create a folder called
Mail
. - Add a new class named
EmailService.cs
:
using GloboTicket.TicketManagement.Application.Contracts.Infrastructure;
using GloboTicket.TicketManagement.Application.Models.Mail;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using Microsoft.Extensions.Logging;
namespace GloboTicket.TicketManagement.Infrastructure.Mail
{
public class EmailService : IEmailService
{
private readonly EmailSettings _emailSettings;
private readonly ILogger<EmailService> _logger;
public EmailService(IOptions<EmailSettings> mailSettings, ILogger<EmailService> logger)
{
_emailSettings = mailSettings.Value;
_logger = logger;
}
public async Task<bool> SendEmail(Email email)
{
var client = new SendGridClient(_emailSettings.ApiKey);
var sendGridMessage = MailHelper.CreateSingleEmail(
new EmailAddress(_emailSettings.FromAddress, _emailSettings.FromName),
new EmailAddress(email.To),
email.Subject,
email.Body,
email.Body);
var response = await client.SendEmailAsync(sendGridMessage);
_logger.LogInformation("Email sent");
return response.StatusCode == System.Net.HttpStatusCode.Accepted || response.StatusCode == System.Net.HttpStatusCode.OK;
}
}
}
Step 3.3: Send Email from the Application Layer
Now let’s use the IEmailService
in the CreateEventCommandHandler
to send an email when an event is created.
- Navigate to
Features/Events/Commands/CreateEvent
. - Open
CreateEventCommandHandler.cs
and modify the code as follows:
using AutoMapper;
using GloboTicket.TicketManagement.Application.Contracts.Infrastructure;
using GloboTicket.TicketManagement.Application.Contracts.Persistence;
using GloboTicket.TicketManagement.Application.Models.Mail;
using GloboTicket.TicketManagement.Domain.Entities;
using MediatR;
namespace GloboTicket.TicketManagement.Application.Features.Events.Commands.CreateEvent
{
public class CreateEventCommandHandler : IRequestHandler<CreateEventCommand, Guid>
{
private readonly IEventRepository _eventRepository;
private readonly IMapper _mapper;
private readonly IEmailService _emailService;
public CreateEventCommandHandler(IMapper mapper, IEventRepository eventRepository, IEmailService emailService)
{
_mapper = mapper;
_eventRepository = eventRepository;
_emailService = emailService;
}
public async Task<Guid> Handle(CreateEventCommand request, CancellationToken cancellationToken)
{
var @event = _mapper.Map<Event>(request);
var validator = new CreateEventCommandValidator(_eventRepository);
var validationResult = await validator.ValidateAsync(request);
if (validationResult.Errors.Count > 0)
throw new Exceptions.ValidationException(validationResult);
@event = await _eventRepository.AddAsync(@event);
// Sending email notification to admin
var email = new Email
{
To = "admin@example.com",
Subject = "A new event was created",
Body = $"A new event was created: {request}"
};
try
{
await _email
Service.SendEmail(email);
}
catch (Exception ex)
{
// Log the error and continue
}
return @event.EventId;
}
}
}
4. Registering Services in the DI Container
To complete the setup, you need to register the EmailService
and CsvExporter
in the DI container.
- In the Infrastructure project, navigate to
InfrastructureServiceRegistration.cs
:
public static class InfrastructureServiceRegistration
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<EmailSettings>(configuration.GetSection("EmailSettings"));
services.AddTransient<IEmailService, EmailService>();
services.AddTransient<ICsvExporter, CsvExporter>();
return services;
}
}
- Make sure to call this method in
Program.cs
of your API project:
builder.Services.AddInfrastructureServices(builder.Configuration);
Conclusion:
In this article, we successfully created the Infrastructure Layer with support for:
-
CSV export using the
CsvHelper
library. - Email sending using the SendGrid service.
By adhering to Clean Architecture principles, we ensured that both the Application Layer and Infrastructure Layer remain decoupled, making the system maintainable and extendable.
For the complete source code, you can visit the GitHub repository: https://github.com/mohamedtayel1980/clean-architecture
Top comments (0)