2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 使用Vue.js和ASP.NET Core MVC实现CQRS模式

使用Vue.js和ASP.NET Core MVC实现CQRS模式

时间:2023-10-06 03:05:16

相关推荐

使用Vue.js和ASP.NET Core MVC实现CQRS模式

目录

介绍

先决条件

深入了解基本信息

应用解决方案结构

图像上传和显示应用

MVC与JS框架之间的通信设计

在SPA中,在表示层中添加UI和PLL

用于数据读取和写入操作的数据访问层

软件包安装

读取查询处理程序的实现

写入命令处理程序

将MediatR与API控制器/控制器集成

配置MediatR

将依赖项注入Web-API控制器/控制器

如何发送命令/查询请求

介绍

如果您是软件专业人员,那么您将熟悉软件增强和维护工作。这是软件开发生命周期的一部分;这样,您就可以纠正故障,删除/增强现有功能。软件维护成本可以最小化如果使用软件体系结构模式,选择合适的技术和了解行业趋势的未来,考虑资源可靠性/可用性为现在和未来,在代码中使用设计模式/原则,重用代码,保持打开你选择未来的扩展,等等。无论如何,如果您在应用程序中使用任何已知的软件架构模式,那么其他人将很容易理解您的应用程序的结构/组件设计。我将使用 Core MVC中的MediatR和Vue.js根据CQRS模式解释示例项目实现。

先决条件

需要.NET Core 3.1的Visual Studio 安装Node.js,NPM和@Vue/Cli需要WebPack和NPM任务运行器

推荐阅读“在 Core 3.1 MVC中集成/配置Vue.js”

深入了解基本信息

CQRS模式:简而言之,命令-查询分离责任(CQRS)模式将在不更改数据库/系统的情况下返回数据的读查询操作与将数据更改到数据库/系统的写命令(插入/更新/删除)操作分离开来。永远不要将读写操作混合在一起。Mediator模式:这是一种设计模式,当您需要集中控制和多个类/对象之间的通信时,将使用中介者,这种设计模式会对代码产生影响。例如,Facebook Messenger是向多个用户发送消息的中介者。MVC模式:这是一个应用程序的架构模式,其中模型、视图和控制器由它们的职责分隔开。模型是对象的数据;视图向用户显示数据并处理用户交互;控制器充当视图和模型之间的中介。

应用解决方案结构

该项目的主要目标是解释CQRS架构模式。我正在努力实现一个小型的单页应用程序(SPA)项目。技术的选择很重要,您应该根据自己的要求进行选择。对于用户界面(UI)和表示逻辑层(PLL),我选择 Core MVC和Vue.js(JavaScript框架)。对于数据访问,我选择实体框架(EF)核心代码优先方法,并将其实现到数据访问层(DAL)中。我特意避免使用单独的业务逻辑层(BLL)和其他层,以最大程度地减少本文的篇幅。

图像上传和显示应用

在本项目中,首先考虑CQRS模式,我将上传图像文件以将其保存到数据库中;它将说明写命令的操作。其次,我将从数据库中读取数据以显示图像;它将说明读取查询操作。

我在同一解决方案中添加了两个单独的项目。一个是名为“HR.App.DAL.CQRS”的ClassLibrary(.NET Core)项目,另一个是名为“HR.App.Web”的 Core Web应用程序项目。

MVC与JS框架之间的通信设计

在此阶段,我要指出UI/PLL以及它们如何相互通信。看下面的图。我将JS框架放在View和Web API Controller之间。

根据上图, MVC控制器呈现视图。JS将来自视图的HTTP请求(GET/PUT/POST/DELETE)传递到Web API控制器,并将Web API控制器的响应数据(JSON/XML)更新到视图。

注意:我正在猜测,如何在 Core MVC项目中配置Vue.js。如果您需要分步说明在带有示例项目的 Core中配置Vue.js,则建议阅读:在 Core 3.1 MVC中集成/配置Vue.js

在SPA中,在表示层中添加UI和PLL

在“HR.App.Web”项目中,添加Index.cshtml视图和Index.cshtml.js文件。我为图像上传和图像视图标签/控件添加了以下HTML脚本到Index.cshtml中。这些与读取和写入操作关联。

@{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>

为HTTP GET添加以下Vue.js脚本,并将其POST请求到Index.cshtml.js文件中:

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));}}});});

在此JS文件中,“viewImageById”方法用于读取请求,而“onUploaded”方法用于写入请求。界面看起来像:

用于数据读取和写入操作的数据访问层

我猜,您知道EF核心代码优先方法,并且您具有域模型和上下文类。您可以使用其他方法。在这里,我将实现读取和写入操作以进行数据访问。查看下图以了解应用程序的整个过程。

软件包安装

在“HR.App.DAL.CQRS”项目中,我已经使用NuGet包管理器安装了MediatR.Extensions.Microsoft.DependencyInjection,Microsoft.EntityFrameworkCore和Microsoft.EntityFrameworkCore.SqlServer。

我需要MediatR来实现命令和查询处理程序。我将为 Core使用MediatR.Extensions来解决依赖关系。

读取查询处理程序的实现

为了从数据库中获取图像,我添加了具有以下代码的GetImageQuery.cs类:

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;}}}

GetImageQuery类继承IRequest<ImageResponse>; ImageResponse类型指示的响应。另一方面,GetImageQueryHandler类继承了IRequestHandler<GetImageQuery, ImageResponse>,其中GetImageQuery类型表示请求/消息,ImageResponse类型表示响应/输出。此GetImageQueryHandler类实现返回ImageResponse对象的Handle方法。

写入命令处理程序

为了将图像保存到数据库中,我添加了SaveImageCommand.cs类,其中包含以下代码:

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.mand{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();mit();response.IsSuccess = true;response.ImageId = request.UploadedImage.ImageId;}catch (Exception exception){trans.Rollback();response.Errors.Add(exception.Message);}return response;}}}}

将MediatR与API控制器/控制器集成

在“HR.App.Web”项目中,我已经使用NuGet软件包管理器安装了MediatR.Extensions.Microsoft.DependencyInjection。它可能会请求安装相关软件包的许可(Microsoft.Extensions.DependencyInjection.Abstractions)。

配置MediatR

在ConfigureServices方法中将以下代码添加到Startup.cs类中进行注册MediatR:

services.AddMediatR(typeof(Startup));

如果您将所有处理程序类都放入 Core MVC项目的同一程序集中(例如,“HR.App.Web”),则此配置效果很好。如果您在同一项目解决方案中使用不同的程序集(例如HR.App.DAL.CQRS),则必须转义以上代码,并需要添加以下代码:

services.AddMediatR(typeof(GetImageQuery));

如果在同一项目解决方案中使用多个程序集(例如AssemblyA和AnotherAssemblyB),则需要添加所有类型的程序集:

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

将依赖项注入Web-API控制器/控制器

在HomeApiController.cs类中,我添加了“SubmitFile”和“GetImageId”操作,这些操作将使用MediatR发送命令和查询对象。下面的代码表明我已经在HomeApiController构造函数中注入了一个依赖Mediator对象。顺便说一下,Web API控制器返回Json/XML数据。

控制器返回视图。在HomeController.cs中,我仅使用默认的Index操作返回视图。

如何发送命令/查询请求

我们可以使用中介对象发送命令/查询对象:mediator.Send(command/query Object);查看下面的代码:

完整的代码如下:

using HR.App.mand;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 & BLif (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)}");}#endregionbyte[] 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 });}}}}

在上面的代码中,“SubmitFile”动作将接收带有IFormFile对象和图像的HttpPost,并将图像转换为字节数组。最后,它使用中介器发送SaveImageCommand对象。

另一方面,GetImageById动作使用imageId接收HttpGet请求,并使用调解器发送查询请求。最后,它处理从字节数组到base64字符串的图像内容,以将其发送到视图。

无论如何,现在如果您运行项目,那么您将看到以下屏幕来上传和查看图像:

在很多情况下,我们需要读写操作才能完成一项任务。例如,我需要更新一个对象的几个属性。首先,我可以调用查询操作来读取对象,然后在放入所需的值之后,可以调用更新操作来存储它。在这种情况下,切勿将读取查询和写入命令混合到同一操作/处理程序中。然后将它们分开,以后将很容易修改。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。