Click here to Skip to main content
14,494,314 members

Implementing CQRS Pattern with Vue.js & ASP.NET Core MVC

Rate this:
4.83 (7 votes)
Please Sign up or sign in to vote.
4.83 (7 votes)
15 Mar 2020CPOL
This article mainly focuses on the CQRS pattern. How we can implement CQRS Pattern using the MediatR. How we can use CQRS pattern with ASP.NET MVC Core and Vue.Js.
In this artice, you will see a sample project implementation according to the CQRS pattern using MediatR in ASP.NET Core MVC with vue.js.

Introduction

If you’re a software professional, then you’re familiar with the Software enhancement and maintenance work. This is the part of software development life cycle; so that, you can correct the faults, delete/ enhance the existing features. The software maintenance cost can be minimized if you use software architectural pattern, choosing right technologies and be aware of the industry trends for the future, consider resource reliability/availability for now and future, use design pattern/principle in your code, re-use your code and keep open your option for future extension, etc. Anyway, if you use any known software architectural pattern in your application, then it will be easy for others to understand the structure/component design of your application. I’ll explain a sample project implementation according to the CQRS pattern using MediatR in ASP.NET Core MVC with vue.js.

Coverage Topics

  • Implementation of CQRS Pattern
  • Configuring MediatR Package for ASP.NET Core
  • Data Read/Write Handler Implantation using MediatR
  • Integrating MediatR with API Controller/Controller
  • Passing Command/Query Object using MediatR
  • Converting image to byte array/byte array to base64 string

Prerequisites

Drilldown the Basic Shorty

  • CQRS Pattern: In short, Command – Query Separation Responsibility (CQRS) pattern separates the READ query operation which returns the data without changing the database/system and WRITE command (insert/update/delete) operation which changes the data into the database/system. NEVER mix the read and write operation together.
  • Mediator Pattern: This is a design pattern that has impact on the code where Mediator is used when you need centralize control and communication between multiple classes/objects. For example, Facebook Messenger is a mediator to send messages to the multiple users.
  • MVC Pattern: This is an architectural pattern for the application where Model, View and Controller are separated by their responsibility. Model is the data of an object; View presents data to the user and handles user interaction; Controller acts like a mediator between View & Model.

Application Solution Structure

The main goal of this project is to explain the CQRS architectural pattern. I’m plaining to implement a tiny Single-Page-Application (SPA) project. The choice of the technology is important, and you should choose it according to your requirements. For the User Interface (UI) and Presentation Logic Layer (PLL), I choose ASP.NET Core MVC and Vue.js (JavaScript framework). For the data access, I choose Entity Framework (EF) Core Code First Approach and it will be implemented into the Data-Access-Layer (DAL). Intentionally, I’m avoiding a separate Business Logic Layer (BLL) and other layers to minimize the length of this article.

Image Upload & Display Application

In this project, considering the CQRS pattern, at first, I will upload the image file to save it into the database; it will explain the write command operation. Secondly, I will read the data from the database to display the image; it will explain the read query operation.

I’ve added two separate projects in the same solution. One is the ClassLibrary (.NET Core) project which is named as “HR.App.DAL.CQRS” and another is the ASP.NET Core Web Application project which is named as “HR.App.Web”.

Image 1

Communication Design Between MVC & JS Framework

In this stage, I’m pointing the UI/PLL and how they will talk to each other. Look at the diagram below. I’m placing the JS framework in between the View and Web API Controller.

Image 2

According to the above diagram, ASP.NET MVC Controller renders the View. JS passes the HTTP request (GET/PUT/POST/DELETE) from the view to the Web API Controller as well as it updates the response data (JSON/XML) from Web API Controller to the View.

Image 3

Note: I’m guessing, you know, how to configure the Vue.js in ASP.NET Core MVC project. If you need the step by step instructions to configure the Vue.js in the ASP.NET Core with sample project, then recommended to read: Integrating/Configuring Vue.js in ASP.NET Core 3.1 MVC

In SPA, Adding the UI and PLL in the Presentation Layer

In the “HR.App.Web” project, add the Index.cshtml view and Index.cshtml.js file. I add the following HTML scripts for the Image upload and Image view tag/control into the Index.cshtml. These are associated with the read and write actions.

@{
    ViewData["Title"] = "Home Page";
}

    <div id="view" v-cloak>
        <div class="card">
            <div class="card-header">
                <div class="row">
                    <div class="col-10">
                        <h5>Upload File</h5>
                    </div>
                </div>
            </div>
            <div class="card-body">
                <dropzone id="uploadDropZone" url="/HomeApi/SubmitFile"

                          :use-custom-dropzone-options="useUploadOptions"

                          :dropzone-options="uploadOptions" v-on:vdropzone-success="onUploaded"

                          v-on:vdropzone-error="onUploadError">
                    <!-- Optional parameters if any! -->
                    <input type="hidden" name="token" value="xxx">
                </dropzone>
            </div>
        </div>
        <br/>
        <div class="card">
            <div class="card-header">
                <div class="row">
                    <div class="col-10">
                        <h5>Image viewer</h5>
                    </div>
                </div>
            </div>
            <div class="card-body">
                <img v-bind:src="imageData" v-bind:alt="imageAlt" style="width:25%;
                 height:25%; display: block;margin-left: auto; margin-right: auto;" />
                <hr />
                <div class="col-6">
                    <button id="viewFile" ref="viewFileRef" type="button" 

                     class="btn btn-info" v-on:click="viewImageById">View Image</button>
                    <button type="button" class="btn btn-info" v-on:click="onClear">
                     Clear</button>
                </div>

            </div>
        </div>
    </div>
<script type="text/javascript">
</script>
<script type="text/javascript" src="~/dest/js/home.bundle.js" 

        asp-append-version="true"></script>

Add the following Vue.js script for the HTTP GET and POST request into the Index.cshtml.js file:

import Vue from 'vue';
import Dropzone from 'vue2-dropzone';

document.addEventListener('DOMContentLoaded', function (event) {
    let view = new Vue({
        el: document.getElementById('view'),
        components: {
            "dropzone": Dropzone
        },
        data: {
            message: 'This is the index page',
            useUploadOptions: true,
            imageData: '',
            imageAlt: 'Image',
            imageId: 0,
            uploadOptions: {
                acceptedFiles: "image/*",
                //acceptedFiles: '.png,.jpg',
                dictDefaultMessage: 'To upload the image click here. Or, drop an image here.',
                maxFiles: 1,
                maxFileSizeInMB: 20,
                addRemoveLinks: true
            }
        },
        methods: {
            onClear() {
                this.imageData = '';
            },
            viewImageById() {
                try {
                    this.dialogErrorMsg = "";
                    //this.imageId = 1;
                    var url = '/HomeApi/GetImageById/' + this.imageId;

                    console.log("===URL===>" + url);
                    var self = this;

                    axios.get(url)
                        .then(response => {
                            let responseData = response.data;

                            if (responseData.status === "Error") {
                                console.log(responseData.message);
                            }
                            else {
                                self.imageData = responseData.imgData;
                                console.log("Image is successfully loaded.");
                            }
                        })
                        .catch(function (error) {
                            console.log(error);
                        });
                } catch (ex) {

                    console.log(ex);
                }
            },
            onUploaded: function (file, response) {
                if (response.status === "OK" || response.status === "200") {
                    let finalResult = response.imageId;
                    this.imageId = finalResult;
                    console.log('Successfully uploaded!');
                }
                else {
                    this.isVisible = false;
                    console.log(response.message);
                }
            },
            onUploadError: function (file, message, xhr) {
                console.log("Message ====> " + JSON.stringify(message));
            }
        }
    });
});

In this JS file, the “viewImageById” method is used for the read request and “onUploaded” method is used for the write request. The screen looks like:

Image 4

Data Access Layer for Data READ & WRITE Operations

I’m guessing, you know the EF Core Code First Approach and you have the domain models and context class. You may use a different approach. Here, I’ll implement the Read and Write operations for data access. Look at the diagram below to understand the whole process of the application.

Image 5

Packages Installation

In the “HR.App.DAL.CQRS” project, I’ve installed MediatR.Extensions.Microsoft.DependencyInjection, Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer“ using NuGet Package Manager.

Image 6

I need the MediatR to implement the Command and Query handlers. I’ll use the MediatR.Extensions for ASP.NET Core to resolve the dependency.

Read Query Handler Implementation

To get an image from the database, I’ve added the GetImageQuery.cs class with the following codes:

using HR.App.DAL.CQRS.Models;
using HR.App.DAL.CQRS.ViewModel;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace HR.App.DAL.CQRS.Query
{
    public class GetImageQuery : IRequest<ImageResponse>
    {
        public int ImageId { get; set; }
    }

    public class GetImageQueryHandler : IRequestHandler<GetImageQuery, ImageResponse>
    {
        private readonly HrAppContext context;

        public GetImageQueryHandler(HrAppContext context)
        {
            this.context = context;
        }

        public async Task<ImageResponse> Handle(GetImageQuery request, CancellationToken cancellationToken)
        {
            ImageResponse imageResponse = new ImageResponse();

            try
            {
                UploadedImage uploadedImage = await context.UploadedImage.AsNoTracking()
                    .Where(x => x.ImageId == request.ImageId).SingleAsync();
                
                if (uploadedImage == null)
                {
                    imageResponse.Errors.Add("No Image found!");
                    return imageResponse;
                }

                imageResponse.UploadedImage = uploadedImage;
                imageResponse.IsSuccess = true;
            }
            catch (Exception exception)
            {
                imageResponse.Errors.Add(exception.Message);
            }

            return imageResponse;
        }
    }
}

The GetImageQuery class inherits IRequest<ImageResponse>; the ImageResponse type indicates the response. On the other hand, the GetImageQueryHandler class inherits the IRequestHandler<GetImageQuery, ImageResponse> where the GetImageQuery type indicates the request/message and the ImageResponse type indicates the response/output. This GetImageQueryHandler class implements the Handle method which returns the ImageResponse object.

WRITE Command Handler

To save an image into the database, I’ve added the SaveImageCommand.cs class which contains the following codes:

using HR.App.DAL.CQRS.Models;
using HR.App.DAL.CQRS.ViewModel;
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace HR.App.DAL.CQRS.Command
{
    public class SaveImageCommand : IRequest<ResponseResult>
    {
        public UploadedImage UploadedImage { get; set; }
    }

    public class SaveImageCommandHandler : IRequestHandler<SaveImageCommand, ResponseResult>
    {
        private readonly HrAppContext context;

        public SaveImageCommandHandler(HrAppContext context)
        {
            this.context = context;
        }

        public async Task<ResponseResult> Handle
               (SaveImageCommand request, CancellationToken cancellationToken)
        {
            using (var trans = context.Database.BeginTransaction())
            {
                ResponseResult response = new ResponseResult();

                try
                {
                    context.Add(request.UploadedImage);
                    await context.SaveChangesAsync();
                    trans.Commit();
                    response.IsSuccess = true;
                    response.ImageId = request.UploadedImage.ImageId;
                }
                catch (Exception exception)
                {
                    trans.Rollback();
                    response.Errors.Add(exception.Message);
                }

                return response;
            }
        }
    }
}

Integrating MediatR with API Controller/Controller

In the “HR.App.Web” project, I’ve installed MediatR.Extensions.Microsoft.DependencyInjection, using NuGet Package Manager. It may ask for the permission to install the dependent package (Microsoft.Extensions.DependencyInjection.Abstractions).

Configuring MediatR

Add the following codes in the ConfigureServices method into the Startup.cs class to register MediatR:

services.AddMediatR(typeof(Startup));

This configuration works well, if you have all the handler classes into the same assembly of the ASP.NET Core MVC project (say, “HR.App.Web”). If you use a different assembly (say, HR.App.DAL.CQRS) into the same project solution, then you have to escape the above code and need to add the following code:

services.AddMediatR(typeof(GetImageQuery));

If you use multiple assemblies (say, AssemblyA and AnotherAssemblyB) in the same project solution, then you need to add all the types of the assemblies:

services.AddMediatR(typeof(AssemblyAClassName), typeof(AnotherAssemblyBClassName));

Injecting Dependency into Web-API Controller/Controller

In the HomeApiController.cs class, I’ve added “SubmitFile” and “GetImageId” actions and these actions will send the command and query objects using MediatR. The below codes indicate that I’ve injected a dependency Mediator object in the HomeApiController constructor. By the way, the web API controller returns the Json/XML data.

Image 7

The Controller returns the view. In the HomeController.cs, I just use the default Index action to return the view.

Image 8

How to Send Command/Query Request

We can send the command/query object using the mediator object: mediator.Send(command/query Object); Look at the code below:

Image 9

The entire code is given below:

using HR.App.DAL.CQRS.Command;
using HR.App.DAL.CQRS.Models;
using HR.App.DAL.CQRS.Query;
using HR.App.DAL.CQRS.ViewModel;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Threading.Tasks;

namespace HR.App.Web.Controllers
{
    [Route("api")]
    [ApiController]
    public class HomeApiController : Controller
    {
        private readonly IMediator mediator;

        public HomeApiController(IMediator mediator)
        {
            this.mediator = mediator;
        }

        [HttpPost("/HomeApi/SubmitFile")]
        public async Task<ActionResult> SubmitFile(IFormFile file)
        {
            try
            {
                #region Validation & BL
                if (file.Length == 0)
                {
                    return Json(new { status = "Error", message = "Image is not found!" });
                }

                if (!file.ContentType.Contains("image"))
                {
                    return Json
                     (new { status = "Error", message = "This is not an image file!" });
                }

                string fileName = file.FileName;

                if (file.FileName.Length > 50)
                {
                    fileName = string.Format($"{file.FileName.Substring(0, 45)}
                               {Path.GetExtension(file.FileName)}");
                }
                #endregion

                byte[] bytes = null;

                using (BinaryReader br = new BinaryReader(file.OpenReadStream()))
                {
                    bytes = br.ReadBytes((int)file.OpenReadStream().Length);
                }

                UploadedImage uploadedImage = new UploadedImage()
                {
                    ImageFileName= fileName,
                    FileContentType = file.ContentType,
                    ImageContent = bytes
                };

                SaveImageCommand saveImageCommand = new SaveImageCommand()
                {
                    UploadedImage = uploadedImage
                };

                ResponseResult responseResult = await mediator.Send(saveImageCommand);

                if (!responseResult.IsSuccess)
                {
                    return Json(new { status = "Error", 
                           message = string.Join("; ", responseResult.Errors) });
                }

                return Json(new { status = "OK", imageId= responseResult.ImageId });
            }
            catch (Exception ex)
            {
                return Json(new { status = "Error", message = ex.Message });
            }
        }

        [HttpGet("/HomeApi/GetImageById/{imageId:int}")]
        public async Task<ActionResult> GetImageById(int imageId)
        {
            try
            {
                ImageResponse imageResponse = await mediator.Send(new GetImageQuery()
                {
                    ImageId = imageId
                });

                UploadedImage uploadedImage = imageResponse.UploadedImage;

                if (!imageResponse.IsSuccess)
                {
                    return Json(new { status = "Error", 
                           message = string.Join("; ", imageResponse.Errors) });
                }
                if (uploadedImage.FileContentType == null || 
                            !uploadedImage.FileContentType.Contains("image"))
                {
                    return Json(new { status = "Error", 
                           message = string.Join("; ", imageResponse.Errors) });
                }

                string imgBase64Data = Convert.ToBase64String(uploadedImage.ImageContent);
                string imgDataURL = string.Format("data:{0};base64,{1}", 
                    uploadedImage.FileContentType, imgBase64Data);
                
                return Json(new { status = "OK", imgData = imgDataURL });
            }
            catch (Exception ex)
            {
                return Json(new { status = "Error", message = ex.Message });
            }
        }
    }
}

In the above codes into the “SubmitFile” action receives the HttpPost with IFormFile object with an image and it Converts image to byte array. Finally, it sends the SaveImageCommand object using mediator.

On the other hand, the GetImageById action receives HttpGet request with an imageId and send a query request using mediator. Finally, it processes the image content from byte array to base64 string to send it to the view.

Anyway, now if you run the project, then you will see the following screen to upload and view image:

Image 10

There’re many cases where we need read and write operations to complete a single task. For example, say, I need to update few properties of an object. First, I can call a query operation to read the object and then, after putting the required values, I can call an update Operation to store it. In this case, NEVER mix-up the read query and write command into the same operation/handler. Keep them separate then, it will be easy to modify in future.

History

  • 16th March, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

HR Rony
Engineer
United States United States
Lazy software engineer in web application development, architecture design, and automated unit testing. Don’t like study; but love to learn. Don’t believe in hard working, spend most of the time to think and work as minimum as I can!

Comments and Discussions

 
GeneralSome thoughts Pin
Klaus Luedenscheidt16-Mar-20 19:34
MemberKlaus Luedenscheidt16-Mar-20 19:34 
GeneralRe: Some thoughts Pin
nitrovent17-Mar-20 0:23
Membernitrovent17-Mar-20 0:23 
GeneralRe: Some thoughts Pin
HR Rony1-Apr-20 7:25
MemberHR Rony1-Apr-20 7:25 
GeneralRe: Some thoughts Pin
HR Rony1-Apr-20 7:22
MemberHR Rony1-Apr-20 7:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 15 Mar 2020

Stats

5.2K views
11 bookmarked