原文

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

表单如下:

这个表单能让用户输入关于组织的一些信息,包括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; }
}

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

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

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何 ...

  2. [译]MediatR, FluentValidation, and Ninject using Decorators

    原文 CQRS 我是CQRS模式的粉丝.对我来说CQRS能让我有更优雅的实现.它同样也有一些缺点:通常需要更多的类,workflow不总是清晰的. MediatR MediatR的文档非常不错,在这就 ...

  3. [译]使用mediatR的notification来扩展的的应用

    原文 你不希望在controller里面出现任何领域知识 开发者经常有这样的疑问"这个代码应该放在哪呢?"应该使用仓储还是query类?.... 怎么去实现职责分离和单一职责呢? ...

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

    原文 你希望保持你的controller足够简单. 你的controller越来越臃肿,你听说command模式是一个给controller瘦身的解决方案. 但是你不知道command模式是否适合你的 ...

  5. 【译】Android NDK API 规范

    [译]Android NDK API 规范 译者按: 修改R代码遇到Lint tool的报错,搜到了这篇文档,aosp仓库地址:Android NDK API Guidelines. 975a589 ...

  6. Drupal与大型网站架构(译)- Large-Scale Web Site Infrastructure and Drupal

    Drupal与大型网站架构(译)- Large-Scale Web Site Infrastructure and Drupal Linuxjournal 网站经典文章翻译,原文地址: Large-S ...

  7. 【译】延迟加载JavaScript

    [译]延迟加载JavaScript 看到一个微信面试题引发的血案 --[译] 什么阻塞了 DOM?中提到的一篇文章,于是决定看下其博客内容,同时翻译下来留作笔记,因英文有限,如有不足之处,欢迎指出.同 ...

  8. REST API设计指导——译自Microsoft REST API Guidelines(四)

    前言 前面我们说了,如果API的设计更规范更合理,在很大程度上能够提高联调的效率,降低沟通成本.那么什么是好的API设计?这里我们不得不提到REST API. 关于REST API的书籍很多,但是完整 ...

  9. 译:4.RabbitMQ Java Client 之 Routing(路由)

    在上篇博文 译:3.RabbitMQ 之Publish/Subscribe(发布和订阅)  我们构建了一个简单的日志系统 我们能够向许多接收者广播日志消息. 在本篇博文中,我们将为其添加一个功能 - ...

随机推荐

  1. noiac132 B君的第三题 (树形dp)

    传送门 本来想用点分治做,结果root又求不对 算的时候还算错了 我好菜啊 结果szr大佬告诉我是树形dp 我好菜啊!! 我们有$\lceil \frac{x}{k} \rceil = \frac{x ...

  2. [ZJOI2015]地震后的幻想乡(期望+dp)

    题目描述 傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们. 这不,幻想乡突然发生了地震,所有的道路都崩塌了.现在的首要任务是尽快让幻想 ...

  3. CentOS下Denyhosts的安装和使用

    安装 默认yum就可以进行安装 yum install denyhosts* -y 配置 配置文件路径: /etc/denyhosts.conf ; YUM安装时其实已经配置好了大部分,我们自己稍作改 ...

  4. 深挖JDK动态代理(一)

     最近在研究RPC框架,避免不了的就是在RPC调用中使用最多的则是动态代理的机制了,基于此,我们先来研究一下JDK动态代理 我们先来尝试着编写一下JDK动态代理的代码 1. 由于JDK动态代理是基于接 ...

  5. QML学习笔记(一)-防止鼠标穿透事件

    作者: 狐狸家的鱼 Github: 八至 1.防止鼠标穿透 MouseArea{ anchors.fill: parent; onClicked: {}; onReleased: {}; onPres ...

  6. ubuntu 百度云

    下载链接:https://pan.baidu.com/s/1HBu5T3PZ8JsS93PgOKnUYw提取密码:6J1A 在终端中输入命令: sudo dpkg -i bcloud_3..1_all ...

  7. JavaScript(JS)之Javascript对象DOM(三)

    https://www.cnblogs.com/haiyan123/p/7598320.html 一.什么是HTML  DOM HTML  Document Object Model(文档对象模型) ...

  8. 如何计算Java对象所占内存的大小

    [ 简单总结: 随便一个java项目,引入jar包: lucene-core-4.0.0.jar 如果是 maven项目,直接用如下依赖: <dependency> <groupId ...

  9. Ubuntu的 g++ gcc版本升降级

    更改gcc默认版本(gcc版本降级/升级) ### 以降级到gcc-6为例 >* sudo apt install gcc-6 g++-6 >* sudo update-alternati ...

  10. ElasticSearch6.5.0 【安装IK分词器】

    不得不夸奖一下ES的周边资源,比如这个IK分词器,紧跟ES的版本,卢本伟牛逼!另外ES更新太快了吧,几乎不到半个月一个小版本就发布了!!目前已经发了6.5.2,估计我还没怎么玩就到7.0了. 下载 分 ...