푸터 콘텐츠로 바로가기
.NET 도움말

CQRS Pattern C# (How It Works For Developers)

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);
}
$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;
    }
}
$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
    }
}
$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" };
        }
    }
}
$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");
    }
}
$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>();
$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");
    }
}
$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);
        }
    }
}
$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.

자주 묻는 질문

소프트웨어 개발에서 CQRS 패턴이란 무엇인가요?

CQRS 패턴 또는 명령 쿼리 책임 분리는 애플리케이션에서 데이터 읽기와 쓰기를 분리하는 설계 전략입니다. 이러한 분리를 통해 명령(쓰기) 및 쿼리(읽기) 작업을 독립적으로 최적화하여 성능과 확장성을 향상시킬 수 있습니다.

CQRS는 .NET 애플리케이션의 성능을 어떻게 향상시킬 수 있나요?

CQRS는 읽기 및 쓰기 작업에 별개의 데이터 모델을 사용하여 개발자가 각 부분을 독립적으로 최적화할 수 있도록 함으로써 .NET 애플리케이션의 성능을 향상시킵니다. 따라서 복잡한 비즈니스 로직을 처리할 때 확장성과 효율성이 향상됩니다.

CQRS 설정에서 MediatR을 사용하면 어떤 이점이 있나요?

MediatR은 .NET 애플리케이션의 구성 요소 간의 결합을 줄여 CQRS를 용이하게 하는 매개자 패턴 라이브러리입니다. 명령, 쿼리 및 해당 핸들러 간의 상호 작용을 관리하는 중개자 역할을 합니다.

IronPDF는 C# 애플리케이션에서 CQRS 패턴을 어떻게 보완하나요?

IronPDF는 강력한 PDF 조작 기능을 제공하여 CQRS 패턴을 보완합니다. 개발자가 애플리케이션 내에서 PDF 문서를 생성, 읽기 및 편집할 수 있으므로 CQRS 설정에서 명령 작업의 일부로 PDF 보고서를 만드는 데 이상적입니다.

ASP.NET Core 프로젝트에서 명령과 쿼리를 분리하는 것이 유리한 이유는 무엇인가요?

ASP.NET Core 프로젝트에서 명령과 쿼리를 분리하면 조직과 명확성이 향상됩니다. 이를 통해 개발자는 각 측면을 독립적으로 관리할 수 있으므로 유지 관리성이 향상되고 CQRS 패턴의 원칙에 부합할 수 있습니다.

종속성 주입은 CQRS 아키텍처에서 어떤 역할을 하나요?

종속성 주입은 명령 및 쿼리 핸들러의 원활한 등록과 제공을 가능하게 하므로 CQRS 아키텍처에서 매우 중요합니다. 이를 통해 ASP.NET Core 애플리케이션은 종속성을 효율적으로 해결하고 필요에 따라 핸들러의 인스턴스를 관리할 수 있습니다.

라이브러리를 사용하여 C#에서 HTML을 PDF로 변환하려면 어떻게 해야 하나요?

IronPDF의 RenderHtmlAsPdf 메서드를 사용하여 HTML 문자열을 PDF로 변환할 수 있습니다. 또한 보고서 및 문서 생성에 유용한 RenderHtmlFileAsPdf를 사용하여 HTML 파일을 PDF로 변환하는 기능도 지원합니다.

구매하기 전에 C# PDF 라이브러리를 평가할 수 있나요?

예, IronPDF는 무료 평가판을 제공하므로 개발자가 구매 결정을 내리기 전에 기능을 살펴볼 수 있습니다.

커티스 차우
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.