Saltar al pie de página
.NET AYUDA

Patrón CQRS C# (Cómo Funciona para Desarrolladores)

Introduction to CQRS

CQRS stands for Command Query Responsibility Segregation. It's a pattern that focuses on separating the reading of data from its writing. This distinction is crucial for several reasons. First, it allows for more flexible optimization of each operation, improving application performance and scalability. When you separate commands (writes) and queries (reads), you can optimize them independently.

For example, a complex application might require fast read operations but can tolerate slower write operations. By applying CQRS, developers can use different data models for reads and writes, segregating the data access layer to tailor to the specific needs of each operation. In this article, we'll explore the concepts of the CQRS pattern and the *IronPDF library for .NET developers.

Core Concepts and Components

The heart of CQRS lies in separating command and query operations, each handling different aspects of data interaction. Understanding these components is crucial for implementing the pattern effectively.

  • Commands: These are responsible for updating data. Commands embody complex business logic and can change the state of data in the data store by acting without returning any information. Commands take the exclusive role of handling write data tasks, directly influencing the state of the application without yielding any output. For instance, adding a new user or updating an existing product's details are actions carried out by commands.

  • Queries: Queries, managed by a query handler, retrieve data or data transfer objects without changing the system's state. They are the questions you ask about your data. For example, fetching a user's profile or listing all products available in an inventory are queries. Queries return data but ensure that they do not modify the data or its state.

One of the popular tools for implementing CQRS in .NET applications is MediatR, a mediator pattern library. It helps to reduce the coupling between components of an application, making them communicate indirectly. MediatR facilitates the handling of commands and queries by mediating between the command/query and its handler.

Practical Implementation with ASP.NET Core

Implementing the CQRS pattern in ASP.NET Core involves setting up your project to separate commands and queries, using a library like MediatR to mediate between them. Here's a simplified overview of how you can set up CQRS in your ASP.NET Core application.

Step 1: Set Up Your ASP.NET Application

  1. Start Visual Studio and choose to create a new project.
  2. Search for and select an "ASP.NET Core Web Application" project type. Click Next.

    CQRS Pattern C# (How It Works For Developers): Figure 1 - Creating a new ASP.NET project

  3. Give your project a name and set its location. Click Create.
  4. Choose the "Web Application (Model-View-Controller)" template for ASP.NET Core. Ensure you're targeting the .NET Core version that suits your requirements. Click Create.

Step 2

Next, you'll want to organize your project for CQRS. You can do this by adding folders to separate commands, queries, and the common interfaces they'll use. In the Solution Explorer, right-click on your project, go to "Add", then "New Folder". Create three folders: "Commands", "Queries", and "Interfaces".

In the "Interfaces" folder, add interfaces for your commands and queries. For a command, you might have an interface ICommandHandler with a method Handle that takes in a command and performs the action. For a query, you could have an interface IQueryHandler with a method Handle that takes in a query and returns data.

CQRS Pattern C# (How It Works For Developers): Figure 2 - Example of how files could be organised

Step 3

Now, let's add a command and query to demonstrate. Suppose your application manages tasks, and you want to add a task (command) and retrieve tasks (query).

In the "Interfaces" folder, add two interfaces:

// Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
// Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
' Define interfaces for your handlers:
Public Interface ICommandHandler(Of TCommand)
	Sub Handle(ByVal command As TCommand)
End Interface

Public Interface IQueryHandler(Of TQuery, TResult)
	Function Handle(ByVal query As TQuery) As TResult
End Interface
$vbLabelText   $csharpLabel

In the "Commands" folder, add a class AddItemCommand with properties for the task details. Also, add a class AddItemCommandHandler that implements ICommandHandler and contains the logic to add a task to the database.

In the "Queries" folder, add a class GetTasksQuery that represents a request for tasks. Add another class GetTasksQueryHandler that implements IQueryHandler and contains the logic to retrieve tasks from the database.

For a simple example, your AddItemCommand might look like this:

public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }

    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }

    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
Public Class AddItemCommand
	Public Property Name() As String
	Public Property Quantity() As Integer

	' Constructor
	Public Sub New(ByVal name As String, ByVal quantity As Integer)
		Me.Name = name
		Me.Quantity = quantity
	End Sub
End Class
$vbLabelText   $csharpLabel

And the AddItemCommandHandler:

public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
Public Class AddItemCommandHandler
	Implements ICommandHandler(Of AddItemCommand)

	Public Sub Handle(ByVal command As AddItemCommand)
		' Here, you'd add the item to your database, for example, to have employee data stored
		Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}")
		' Add database logic here
	End Sub
End Class
$vbLabelText   $csharpLabel

Your GetItemsQuery could be empty if it doesn't need any parameters to fetch tasks, and GetItemsQueryHandler might look like:

public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}

namespace CQRS_testing.Queries
{
    using CQRS_testing.Interfaces;

    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}

namespace CQRS_testing.Queries
{
    using CQRS_testing.Interfaces;

    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
Imports CQRS_testing.Interfaces

Public Class GetItemsQuery
	' This class might not need any properties, depending on your query
End Class

Namespace CQRS_testing.Queries

	Public Class GetItemsQueryHandler
		Implements IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))

		Public Function Handle(ByVal query As GetItemsQuery) As IEnumerable(Of String)
			' Here, you'd fetch items from your database
			Return New List(Of String) From {"Item1", "Item2"}
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

In your ASP.NET controllers, you'll use these handlers to process commands and queries. For adding a task, the controller action would create an AddTaskCommand, set its properties from the form data, and then pass it to an AddTaskCommandHandler instance to handle. For retrieving tasks, it would call a GetTasksQueryHandler to get the data and pass it to the view.

Wiring it Up in a Controller

With your commands and queries set up, you can now use them in your controllers. Here's how you might do that in an ItemsController class:

public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;

    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }

    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }

    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;

    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }

    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }

    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
Public Class ItemsController
	Inherits Controller

	Private ReadOnly _addItemHandler As ICommandHandler(Of AddItemCommand)
	Private ReadOnly _getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))

	' Constructor injection is correctly utilized here
	Public Sub New(ByVal addItemHandler As ICommandHandler(Of AddItemCommand), ByVal getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)))
		_addItemHandler = addItemHandler
		_getItemsHandler = getItemsHandler
	End Sub

	Public Function Index() As IActionResult
		' Use the injected _getItemsHandler instead of creating a new instance
		Dim query = New GetItemsQuery()
		Dim items = _getItemsHandler.Handle(query)
		Return View(items)
	End Function

	<HttpPost>
	Public Function Add(ByVal name As String, ByVal quantity As Integer) As IActionResult
		' Use the injected _addItemHandler instead of creating a new instance
		Dim command = New AddItemCommand(name, quantity)
		_addItemHandler.Handle(command)
		Return RedirectToAction("Index")
	End Function
End Class
$vbLabelText   $csharpLabel

To wire everything up, especially if you're using Dependency Injection (DI) in ASP.NET Core, you'll need to register your command and query handlers with the DI container in the Startup.cs file. This way, ASP.NET can provide instances of your handlers when they're needed.

Here's a very basic example of registering a handler:

builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient(Of ICommandHandler(Of AddItemCommand), AddItemCommandHandler)()
builder.Services.AddTransient(Of IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)), GetItemsQueryHandler)()
$vbLabelText   $csharpLabel

In the practical application of CQRS, the distinction between the data model for write operations and the one for read operations is foundational, ensuring that the architecture supports varied and optimized approaches to handling data.

IronPDF: C# PDF Library

CQRS Pattern C# (How It Works For Developers): Figure 3 - IronPDF webpage

Explore IronPDF for PDF management is a tool for developers working with the C# programming language, allowing them to create, read, and edit PDF documents directly within their applications. This library is user-friendly, making it simpler to integrate PDF functionalities such as generating PDF reports, invoices, or creating PDFs from HTML code. IronPDF supports various features, including editing text and images in PDFs, setting up document security, and converting webpages to PDF format. Its versatility and ease of use make it a valuable resource for developers looking to implement PDF operations in their projects.

IronPDF excels with its HTML to PDF conversion capability, keeping all layouts and styles intact. It creates PDFs from web content, suitable for reports, invoices, and documentation. HTML files, URLs, and HTML strings can be converted to PDFs seamlessly.

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

Code Example

Now, let's explore how IronPDF can be utilized within a C# application following the Command Query Responsibility Segregation (CQRS) pattern. Below is a simplified example demonstrating how you might use IronPDF within a CQRS setup to generate a PDF report. This example is conceptual and focuses on the generation of a PDF document as a command.

using IronPdf;
using System.Threading.Tasks;

namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
using IronPdf;
using System.Threading.Tasks;

namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
Imports IronPdf
Imports System.Threading.Tasks

Namespace PdfGenerationApp.Commands
	Public Class GeneratePdfReportCommand
		' Command handler that generates a PDF report
		Public Async Function GenerateReportAsync(ByVal reportContent As String, ByVal outputPath As String) As Task
			' Initialize the IronPDF HTML to PDF renderer
			Dim renderer = New ChromePdfRenderer()
			' Use IronPDF to generate a PDF from HTML content
			Dim pdf = Await Task.Run(Function() renderer.RenderHtmlAsPdf(reportContent))
			' Save the generated PDF to a specified path
			pdf.SaveAs(outputPath)
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

In this example, GeneratePdfReportCommand represents a command in the CQRS pattern. It includes a method GenerateReportAsync that takes in reportContent as an HTML string and an outputPath where the PDF report will be saved. IronPDF's HtmlToPdf class is used to convert the HTML content to PDF format, which is then saved to the specified path. This setup illustrates how you can integrate PDF generation functionality into your application's architecture, especially in scenarios requiring clear separation of concerns as promoted by CQRS.

CQRS Pattern C# (How It Works For Developers): Figure 4 - Outputted PDF

Conclusion

CQRS Pattern C# (How It Works For Developers): Figure 5 - IronPDF license information

To wrap up, the Command Query Responsibility Segregation (CQRS) pattern offers a structured approach to separating the responsibilities of reading and writing data in your applications. This separation not only clarifies the architecture but also enhances the flexibility, scalability, and performance of your systems. By following the steps outlined above, you can implement CQRS in your ASP.NET Core applications, using tools like MediatR to streamline communication between commands, queries, and their handlers.

Integrating IronPDF into your CQRS-based application further expands its capabilities, enabling you to effortlessly create, manipulate, and store PDF documents. Whether you're generating reports, invoices, or any form of document, IronPDF's comprehensive features and straightforward syntax make it a powerful tool in your development toolkit. IronPDF offers a free trial, giving you the chance to explore its capabilities before committing. For continued use, licenses start from $799, providing various options to match your project's needs.

Preguntas Frecuentes

¿Qué es el patrón CQRS en el desarrollo de software?

El patrón CQRS, o Segregación de Responsabilidad de Comandos y Consultas, es una estrategia de diseño que separa la lectura de datos de la escritura en aplicaciones. Esta separación permite la optimización independiente de las operaciones de comando (escritura) y consulta (lectura), mejorando el rendimiento y la escalabilidad.

¿Cómo puede mejorar el rendimiento de las aplicaciones .NET el CQRS?

CQRS mejora el rendimiento de las aplicaciones .NET utilizando modelos de datos distintos para las operaciones de lectura y escritura, lo que permite a los desarrolladores optimizar cada parte de manera independiente. Esto lleva a una mejor escalabilidad y eficiencia en el manejo de lógica de negocio compleja.

¿Cuáles son las ventajas de usar MediatR en una configuración CQRS?

MediatR es una biblioteca del patrón mediador que facilita CQRS al reducir el acoplamiento entre componentes en aplicaciones .NET. Actúa como intermediario, gestionando las interacciones entre comandos, consultas y sus manejadores.

¿Cómo complementa IronPDF el patrón CQRS en aplicaciones C#?

IronPDF complementa el patrón CQRS proporcionando capacidades robustas de manipulación de PDF. Permite a los desarrolladores generar, leer y editar documentos PDF dentro de aplicaciones, siendo ideal para crear informes PDF como parte de las operaciones de comando en una configuración CQRS.

¿Por qué es beneficioso separar comandos y consultas en un proyecto ASP.NET Core?

Separar comandos y consultas en un proyecto ASP.NET Core mejora la organización y claridad. Permite a los desarrolladores gestionar cada aspecto de manera independiente, mejorando la mantenibilidad y alineándose con los principios del patrón CQRS.

¿Qué papel juega la inyección de dependencia en una arquitectura CQRS?

La inyección de dependencia es crucial en una arquitectura CQRS ya que permite el registro y provisión sin problemas de manejadores de comandos y consultas. Esto asegura que las aplicaciones ASP.NET Core puedan resolver eficientemente las dependencias y gestionar instancias de manejadores según sea necesario.

¿Cómo puedo convertir HTML a PDF en C# usando una biblioteca?

Puede usar el método RenderHtmlAsPdf de IronPDF para convertir cadenas de HTML en PDFs. También admite la conversión de archivos HTML a PDFs usando RenderHtmlFileAsPdf, lo cual es útil para generar informes y documentación.

¿Puedo evaluar una biblioteca de PDF para C# antes de comprarla?

Sí, IronPDF ofrece una versión de prueba gratuita, permitiendo a los desarrolladores explorar sus características y capacidades antes de tomar una decisión de compra.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más