[译]A NON-TRIVIAL EXAMPLE OF MEDIATR USAGE

原文

来看看我目前的一个项目。这个是一个多租户的财务跟踪系统。有一个组织继承的关系。首先得新建一个组织。

表单如下:

这个表单能让用户输入关于组织的一些信息,包括active directory组,一个唯一的简写名。在客户端使用ajax确保active directory组存在。

POST Action如下:

// POST: Organizations/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(OrganizationEditorForm form)
{
    Logger.Trace("Create::Post::{0}", form.ParentOrganizationId);

    if (ModelState.IsValid)
    {
        var command = new AddOrEditOrganizationCommand(form, ModelState);
        var result = await mediator.SendAsync(command);

        if(result.IsSuccess)
            return RedirectToAction("Index", new { Id = result.Result });

        ModelState.AddModelError("", result.Result.ToString());
     }

     return View(form);
}

基于mvc的模型绑定,绑定view model和做一些基础的基于datannotation的数据验证。如果验证失败,定向到表单页面并显示ModelState的错误。

接下来我构造了一个AddOrEditOrganzationCommand,它包含了view model和当前的ModelState。这能让我们将在服务端的验证结果附加到ModelState上。这个command对象只是简单的包含了我们需要的数据。

public class AddOrEditOrganizationCommand : IAsyncRequest<ICommandResult>
{
    public OrganizationEditorForm Editor { get; set; }
    public ModelStateDictionary ModelState { get; set; }

    public AddOrEditOrganizationCommand(OrganizationEditorForm editor,
        ModelStateDictionary modelState)
    {
        Editor = editor;
        ModelState = modelState;
    }
}

这个command通过mediator来发送,返回一个结果。我的结果类型是 (SuccessResult 和 FailureResult) ,基于下面的接口:

public interface ICommandResult
{
    bool IsSuccess { get; }
    bool IsFailure { get; }
    object Result { get; set; }
 }

如果是成功的结果,重定向到用户最近创建的组织的详细页。如果失败,将失败的消息添加到ModelState中,并在form页面显示。

现在我们需要handler来处理命令。

public class OrganizationEditorFormValidatorHandler : CommandValidator<AddOrEditOrganizationCommand>
    {
        private readonly ApplicationDbContext context;

        public OrganizationEditorFormValidatorHandler(ApplicationDbContext context)
        {
            this.context = context;
            Validators = new Action<AddOrEditOrganizationCommand>[]
            {
                EnsureNameIsUnique, EnsureGroupIsUnique, EnsureAbbreviationIsUnique
            };
        }

        public void EnsureNameIsUnique(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("EnsureNameIsUnique::{0}", message.Editor.Name);

            var isUnique = !context.Organizations
                .Where(o => o.Id != message.Editor.OrganizationId)
                .Any(o => o.Name.Equals(message.Editor.Name,
                        StringComparison.InvariantCultureIgnoreCase));

            if(isUnique)
                return;

            message.ModelState.AddModelError("Name",
                    "The organization name ({0}) is in use by another organization."
                    .FormatWith(message.Editor.Name));
        }

        public void EnsureGroupIsUnique(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("EnsureGroupIsUnique::{0}", message.Editor.GroupName);

            var isUnique = !context.Organizations
                .Where(o => o.Id != message.Editor.OrganizationId)
                .Any(o => o.GroupName.Equals(message.Editor.GroupName,
                        StringComparison.InvariantCultureIgnoreCase));

            if (isUnique)
                return;

            message.ModelState.AddModelError("Group",
                "The Active Directory Group ({0}) is in use by another organization."
                    .FormatWith(message.Editor.GroupName));
        }

        public void EnsureAbbreviationIsUnique(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("EnsureAbbreviationIsUnique::{0}",
                    message.Editor.Abbreviation);

            var isUnique = !context.Organizations
                .Where(o => o.Id != message.Editor.OrganizationId)
                .Any(o => o.Abbreviation.Equals(message.Editor.Abbreviation,
                        StringComparison.InvariantCultureIgnoreCase));

            if (isUnique)
                return;

            message.ModelState.AddModelError("Abbreviation",
                    "The Abbreviation ({0}) is in use by another organization."
                        .FormatWith(message.Editor.Name));
        }
    }

CommandValidator包含一些简单的帮助方法,用来迭代定义的验证方法并执行他们。每个验证方法执行一些特别的逻辑,并在出错的时候将错误消息添加到ModelState。

下面的command handler是将表单的信息存储到数据库中。

public class AddOrEditOrganizationCommandHandler : IAsyncRequestHandler<AddOrEditOrganizationCommand, ICommandResult>
    {
        public ILogger Logger { get; set; }

        private readonly ApplicationDbContext context;

        public AddOrEditOrganizationCommandHandler(ApplicationDbContext context)
        {
            this.context = context;
        }

        public async Task<ICommandResult> Handle(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("Handle");

            if (message.ModelState.NotValid())
                return new FailureResult("Validation Failed");

            if (message.Editor.OrganizationId.HasValue)
                return await Edit(message);

            return await Add(message);
        }

        private async Task<ICommandResult> Add(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("Add");

            var organization = message.Editor.BuildOrganiation(context);

            context.Organizations.Add(organization);

            await context.SaveChangesAsync();

            Logger.Information("Add::Success Id:{0}", organization.Id);

            return new SuccessResult(organization.Id);
        }

        private async Task<ICommandResult> Edit(AddOrEditOrganizationCommand message)
        {
            Logger.Trace("Edit::{0}", message.Editor.OrganizationId);

            var organization = context.Organizations
                    .Find(message.Editor.OrganizationId);

            message.Editor.UpdateOrganization(organization);

            await context.SaveChangesAsync();

            Logger.Information("Edit::Success Id:{0}", organization.Id);

            return new SuccessResult(organization.Id);
        }
    }

这个handle非常简单。首先检查上一次的验证结果,如果失败直接返回失败结果。然后根据ID判断是执行新增还是编辑方法。

现在我们还没有为组织启用或禁用feature。我想将保存组织信息和处理feature的代码逻辑分隔开。

因此我新增一个UpdateOrganizationFeaturesPostHandler来处理feature。

public class UpdateOrganizationFeaturesPostHandler : IAsyncPostRequestHandler<AddOrEditOrganizationCommand, ICommandResult>
    {
        public ILogger Logger { get; set; }

        private readonly ApplicationDbContext context;

        public UpdateOrganizationFeaturesPostHandler(ApplicationDbContext context)
        {
            this.context = context;
        }

        public async Task Handle(AddOrEditOrganizationCommand command,
            ICommandResult result)
        {
            Logger.Trace("Handle");

            if (result.IsFailure)
                return;

            var organization = await context.Organizations
                                        .Include(o => o.Features)
                                        .FirstAsync(o => o.Id == (int) result.Result);

            var enabledFeatures = command.Editor.EnabledFeatures
                                    .Select(int.Parse).ToArray();

            //disable features
            organization.Features
                .Where(f => !enabledFeatures.Contains(f.Id))
                .ToArray()
                .ForEach(f => organization.Features.Remove(f));

            //enable features
            context.Features
                .Where(f => enabledFeatures.Contains(f.Id))
                .ToArray()
                .ForEach(organization.Features.Add);

            await context.SaveChangesAsync();
        }
    }

Create的Get Action如下:

// GET: Organizations/Create/{1}
public async Task<ActionResult> Create(int? id)
{
    Logger.Trace("Create::Get::{0}", id);

    var query = new OrganizationEditorFormQuery(parentOrganizationId: id);
    var form = await mediator.SendAsync(query);
    return View(form);
 }

模型绑定如下:

[ModelBinderType(typeof(OrganizationEditorForm))]
public class OrganizationEditorFormModelBinder : DefaultModelBinder
{
    public ILogger Logger { get; set; }

    private readonly ApplicationDbContext dbContext;

    public OrganizationEditorFormModelBinder(ApplicationDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public override object BindModel(ControllerContext controllerContext,
        ModelBindingContext bindingContext)
    {
        Logger.Trace("BindModel");

        var form = base.BindModel(controllerContext, bindingContext)
                .CastOrDefault<OrganizationEditorForm>();

        if (form.ParentOrganizationId.HasValue)
            form.ParentOrganization = dbContext.Organizations
                .FirstOrDefault(o => o.Id == form.ParentOrganizationId);

        return form;

    }
}

原文地址:https://www.cnblogs.com/irocker/p/a-non-trivial-example-of-mediatr-usage.html

时间: 2024-10-31 02:08:12

[译]A NON-TRIVIAL EXAMPLE OF MEDIATR USAGE的相关文章

[译]ASP.NET Core中使用MediatR实现命令和中介者模式

作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何在ASP.NET Core中使用它来解决我们的问题并使代码简洁.因此,我们将通过下面的主题来进行相关的讲解. 什么是命令模式? 命令模式的简单实例以及中介者模式的简单描述 MVC中的瘦控制器是什么?我们是如如何实现使控制器变瘦的? 我们如何在我们的.NET Core应用程序中使用MediatR 使用

[译]使用Command模式和MediatR简化你的控制器

原文 你希望保持你的controller足够简单. 你的controller越来越臃肿,你听说command模式是一个给controller瘦身的解决方案. 但是你不知道command模式是否适合你的应用.应该有多少command? 特性 command模式最好的一点是你可以先不管业务的实现,先聚焦于用户交互界面. 假设你创建了一个网站,有一个功能是注册和登陆. Hello MediatR 实现command模式非常简单,特别是当你使用了Jimmy Bogard's MediatR来发送来自MV

【JavaScript】【译】编写高性能JavaScript

英文链接:Writing Fast, Memory-Efficient JavaScript 很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执行的大型JavaScript应用所设计的.如果你是一个开发者,并且关心内存使用情况与页面性能,你应该了解用户浏览器中的JavaScript引擎是如何运作的.无论是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,这样做可以帮助你更好地优

高级C#信使(译) - Unity维基百科

高级C#信使 作者:Ilya Suzdalnitski 译自:http://wiki.unity3d.com/index.php/Advanced_CSharp_Messenger 描述 前言 MissingReferenceException的原因和解决方案 信使 用法 事件监听器 注册事件监听器 注销事件监听器 广播事件 清空信使 永久信使 杂项 打印所有消息 从其他信使过渡 代码 Callback.cs Messenger.cs 描述 这是C#的一个高级版本的消息系统.当加载了一个新的场景

Unity性能优化(2)-官方文档简译

本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 简介 如果游戏运行缓慢,卡顿,我们知道游戏存在性能问题.在我们尝试解决问题前,需要先知道引起问题的原因.不同问题需要不同的解决方案.如果我们靠猜测或者其他项目的经验去解决问题,那么我们可能会浪费很多时间,甚至使得问题更严重. 这时我们需要性能分析,性能分析程序测量游戏运行时的各个方面性能.通过性能分析工具,我们能够透过

基于TCP的TFTP(Trivial File Transfer Protocol,简单文件传输协议) 的c编程实现

我们或许都听到过,TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂.开销不大的文件传输服务. 本文就简单的叙述下tftp的小文件传输功能以及客户端对服务器的列表功能. 之前就一直很纳闷,我们经常在网上下载什么东西或者从别处传输一个文件,具体是怎么实现的呢?于是乎,翻查一些资料,加上自己对网络编程的逐步加深,所以功夫不负有心人,还算是大致的完成了下. 本例程实现的功能呢?

Use Reentrant Functions for Safer Signal Handling(译:使用可重入函数进行更安全的信号处理)

Use Reentrant Functions for Safer Signal Handling 使用可重入函数进行更安全的信号处理 How and when to employ reentrancy to keep your code bug free 何时及如何利用可重入性避免代码缺陷 Dipak Jha (mailto:[email protected]?subject=Use reentrant functions for safer signal handling&[email pr

使用Boost.Python构建混合系统(译)

目录 Building Hybrid Systems with Boost.Python 摘要(Abstract) 介绍(Introduction) 设计目标 (Boost.Python Design Goals) Hello Boost.Python World 库概述 (Library Overview) 类公开 (Exposing Classes) 序列化 Serialization 对象接口 Object interface 考虑混合编程 Thinking hybrid 开发历史 Dev

CSharpGL(56)[译]Vulkan入门(转)

阅读目录(Content) Background 背景 System Setup 系统安装 Linux Windows Building and Running 建设和运行 Linux Windows General Comments 基础命令 Code Structure 代码结构 Source walkthru 源代码浏览 CSharpGL(56)[译]Vulkan入门 本文是对(http://ogldev.atspace.co.uk/www/tutorial50/tutorial50.ht