Incoding Framework - 入门
免责声明:本文是一篇分步指南,旨在帮助您熟悉 Incoding Framework 的核心功能。遵循本指南将构建一个应用程序,该应用程序实现了与数据库的交互(CRUD + 数据过滤器),并附带单元测试。
- 下载 incoding-framework-get-started.zip - 701.9 KB (nuget 应 还原所有包)
让我们从对框架的简要描述开始。Incoding Framework 包含三个包:Incoding framework – 后端项目,Incoding Meta Language – 前端项目,以及 Incoding tests helpers – 后端单元测试。这些包可以独立安装,从而可以在项目中分部分集成框架:您可以仅连接前端或后端(测试与后端紧密耦合,因此它们可以被视为补充)。
使用 Incoding Framework 开发的项目,采用 CQRS 作为服务器架构。 Incoding Meta Language。Incoding Framework 用作构建前端的基本工具。总而言之,Incoding Framework 涵盖了整个应用程序开发周期。
使用 Incoding Framework 开发的典型解决方案包含 3 个项目
- Domain(类库)- 负责业务逻辑和数据库操作。
- UI(ASP.NET MVC 项目)- 基于 ASP.NET MVC 的前端。
- UnitTests(类库)- Domain 的单元测试。
定义域
通过 Nuget 安装 Incoding framework 后,除了必要的 dll,项目还会添加 Bootstrapper.cs 文件。该文件主要负责应用程序的初始化:日志初始化、IoC 注册、Ajax 请求设置等。默认情况下,StructureMap 被用作 IoC 框架,但也有 Ninject 的提供程序,并且可以编写自己的实现。
namespace Example.Domain
{
#region << Using >>
using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentValidation;
using FluentValidation.Mvc;
using Incoding.Block.IoC;
using Incoding.Block.Logging;
using Incoding.CQRS;
using Incoding.Data;
using Incoding.EventBroker;
using Incoding.Extensions;
using Incoding.MvcContrib;
using NHibernate.Tool.hbm2ddl;
using StructureMap.Graph;
#endregion
public static class Bootstrapper
{
public static void Start()
{
//Initialize LoggingFactory
LoggingFactory.Instance.Initialize(logging =>
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");
logging.WithPolicy(policy => policy.For(LogType.Debug).Use(FileLogger.WithAtOnceReplace(path,
() => "Debug_{0}.txt".F(DateTime.Now.ToString("yyyyMMdd")))));
});
//Initialize IoCFactory
IoCFactory.Instance.Initialize(init =>
init.WithProvider(new StructureMapIoCProvider(registry =>
{
registry.For<IDispatcher>().Use<DefaultDispatcher>();
registry.For<IEventBroker>().Use<DefaultEventBroker>();
registry.For<ITemplateFactory>().Singleton().Use<TemplateHandlebarsFactory>();
//Configure FluentlyNhibernate
var configure = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["Example"].ConnectionString))
.Mappings(configuration => configuration.FluentMappings
.AddFromAssembly(typeof(Bootstrapper).Assembly))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.CurrentSessionContext<NhibernateSessionContext>();
registry.For<INhibernateSessionFactory>()
.Singleton()
.Use(() => new NhibernateSessionFactory(configure));
registry.For<IUnitOfWorkFactory>().Use<NhibernateUnitOfWorkFactory>();
registry.For<IRepository>().Use<NhibernateRepository>();
//Scan currenlty Assembly and registrations all Validators and Event Subscribers
registry.Scan(r =>
{
r.TheCallingAssembly();
r.WithDefaultConventions();
r.ConnectImplementationsToTypesClosing(typeof(AbstractValidator<>));
r.ConnectImplementationsToTypesClosing(typeof(IEventSubscriber<>));
r.AddAllTypesOf<ISetUp>();
});
})));
ModelValidatorProviders.Providers
.Add(new FluentValidationModelValidatorProvider(new IncValidatorFactory()));
FluentValidationModelValidatorProvider.Configure();
//Execute all SetUp
foreach (var setUp in IoCFactory.Instance.ResolveAll<ISetUp>().OrderBy(r => r.GetOrder()))
{
setUp.Execute();
}
var ajaxDef = JqueryAjaxOptions.Default;
ajaxDef.Cache = false; //Disable Ajax cache
}
}
}
接下来,在 Domain 中添加命令(Command)和查询(Query),它们负责数据库操作或与业务应用程序逻辑相关的任何操作。
UI
安装 Incoding Meta Language 包后,它会添加必要的 dll,以及用于 Domain 工作所需的 IncodingStart.cs 和 DispatcherController.cs(部分 MVD)。
public static class IncodingStart
{
public static void PreStart()
{
Bootstrapper.Start();
new DispatcherController(); // init routes
}
}
public class DispatcherController : DispatcherControllerBase
{
#region Constructors
public DispatcherController()
: base(typeof(Bootstrapper).Assembly) { }
#endregion
}
安装后,使用 IML 将客户端逻辑添加到 UI。
UnitTests
安装 Incoding tests helpers 时,项目会添加 MSpecAssemblyContext.cs 文件,其中自定义了到测试数据库的连接。
public class MSpecAssemblyContext : IAssemblyContext
{
#region IAssemblyContext Members
public void OnAssemblyStart()
{
//Configuration data base
var configure = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["Example_Test"].ConnectionString)
.ShowSql())
.Mappings(configuration => configuration.FluentMappings
.AddFromAssembly(typeof(Bootstrapper).Assembly));
PleasureForData.StartNhibernate(configure, true);
}
public void OnAssemblyComplete() { }
#endregion
}
第 1 部分。安装。
那么,我们继续进行免责声明中的任务,开始编写我们的应用程序。构建应用程序的第一阶段是创建项目的解决方案结构并将其添加到其中。项目解决方案将命名为 Example,并且如引言中所述,将包含 3 个项目。我们从负责应用程序业务逻辑的项目 - Domain 开始。
创建类库 Domain。
然后我们转向前端 – 创建并安装 ASP.NET Web 应用程序 UI,并选择 MVC 包作为模板,空项目。
最后,我们添加负责单元测试的类库 UnitTests。
注意:虽然 UnitTests 不是应用程序的强制部分,但我们建议您用测试覆盖代码,因为这将有助于避免将来因测试自动化而产生的各种代码故障。
完成以上所有活动后,您将获得如下解决方案: 创建解决方案结构后,我们需要从 Nuget 安装 Incoding Framework 包。安装通过 Nuget 进行。所有项目的安装算法都相同
- 右键单击项目,然后在上下文菜单中选择“管理 Nuget 包…”
- 搜索 incoding
- 选择必要的包并安装它
首先在 Domain 中安装 Incoding framework。
然后将 StructureMap.Graph 的链接添加到 Domain -> Infrastructure -> Bootstrapper.cs 文件。
注意:请确保 References -> System.Web.Mvc.dll 的 Copy Local 属性设置为 true。
现在,修改 Example.UI -> Views -> Shared -> _Layout.cshtml 文件,使其如下所示。
@using Incoding.MvcContrib
<!DOCTYPE html>
<html >
<head>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-ui-1.10.2.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/underscore.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.form.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.history.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/handlebars-1.1.2.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/incoding.framework.min.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/incoding.meta.language.contrib.js")"> </script>
<script type="text/javascript" src="@Url.Content("~/Scripts/bootstrap.min.js")"> </script>
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/bootstrap.min.css")">
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.core.css")">
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.datepicker.css")">
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.dialog.css")">
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.theme.css")">
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.menu.css")">
<script>
TemplateFactory.Version = '@Guid.NewGuid().ToString()';
</script>
</head>
@Html.Incoding().RenderDropDownTemplate()
<body>
@RenderBody()
</body>
</html>
然后将 Bootstrapper.cs 的链接添加到 Example.UI -> App_Start -> IncodingStart.cs 和 Example.UI -> 文件中。
Controllers -> DispatcherController.cs.
注意:如果您使用 MVC5,则框架需要在 Web.config 文件中添加以下代码。
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
现在,在 UnitTests 中安装 Incoding tests helpers,并将 Bootstrapper.cs 的链接添加到 Example.UnitTests -> MSpecAssemblyContext.cs。
准备项目工作的最后阶段是创建项目的文件夹结构。向 Example.Domain 项目添加以下文件夹:
- Operations – 项目的命令和查询
- Persistences – 用于数据库映射的实体
- Specifications – 在请求数据时用于数据清理的条件和排序
在 Example.UnitTests 项目中创建与 Example.Domain 相同的文件夹结构。
第 2 部分。设置数据库连接。
要开始此过程,请创建您要使用的数据库。打开 SQL Management Studio 并创建两个数据库:Example 和 Example_test。
为了与数据库交互,您需要设置连接。将连接字符串添加到 Example.UI -> Web.config 和 Example.UnitTests -> app.config 文件中。
<connectionStrings>
<add name="Example" connectionString="Data Source=INCODING-PC\SQLEXPRESS;Database=Example;Integrated Security=false; User Id=sa;Password=1" providerName="System.Data.SqlClient" />
<add name="Example_Test" connectionString="Data Source=INCODING-PC\SQLEXPRESS;Database=Example_Test;Integrated Security=true" providerName="System.Data.SqlClient" />
</connectionStrings>
在 Example.Domain -> Infrastructure -> Bootstrapper.cs 文件中,使用名为 Example 的键注册相应的连接字符串。
//Configure FluentlyNhibernate
var configure = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager
.ConnectionStrings["Example"].ConnectionString))
.Mappings(configuration => configuration.FluentMappings
.AddFromAssembly(typeof(Bootstrapper).Assembly))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.CurrentSessionContext(); //Configure data base
在 Example.UnitTests -> MSpecAssemblyContext.cs 文件中,使用名为 Example_test 的键注册到数据库的连接字符串。
//Configure connection to Test data base
var configure = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings["Example_Test"].ConnectionString)
.ShowSql())
.Mappings(configuration => configuration.FluentMappings
.AddFromAssembly(typeof(Bootstrapper).Assembly));
注意:Example 和 Example_test 数据库必须存在。
第 3 部分。CRUD。
在执行上述操作后,我们来到最有趣的部分——编写实现应用程序 CRUD(创建、读取、更新、删除)功能的代码。为了开始这个过程,创建一个映射到数据库的实体类。在本例中,它是 Human.cs,我们将其添加到 Example.Domain -> Persistences 文件夹。
Human.cs
namespace Example.Domain
{
#region << Using >>
using System;
using Incoding.Data;
#endregion
public class Human : IncEntityBase
{
#region Properties
public virtual DateTime Birthday { get; set; }
public virtual string FirstName { get; set; }
public virtual string Id { get; set; }
public virtual string LastName { get; set; }
public virtual Sex Sex { get; set; }
#endregion
#region Nested Classes
public class Map : NHibernateEntityMap<Human>
{
#region Constructors
protected Map()
{
IdGenerateByGuid(r => r.Id);
MapEscaping(r => r.FirstName);
MapEscaping(r => r.LastName);
MapEscaping(r => r.Birthday);
MapEscaping(r => r.Sex);
}
#endregion
}
#endregion
}
public enum Sex
{
Male = 1,
Female = 2
}
}
我们的类包含几个字段,我们将在其中写入数据,以及一个嵌套类 Map。
注意:创建 Human 类后,由于 FluentNhibernate,您无需执行任何操作(创建 XML 映射)。
现在我们可以添加负责 CRUD 操作实现的命令和查询。第一个命令将负责添加新记录或更改现有的 Human 类型记录。该命令非常简单:我们通过键(Id)从 Repository 获取实体,或者,如果没有实体,则创建一个新实体。这两个实体都将获取 AddOrEditHumanCommand 类属性中指定的值。将 Example.Domain -> Operations -> AddOrEditHumanCommand.cs 添加到项目中。
AddOrEditHumanCommand.cs
namespace Example.Domain { #region << Using >> using System; using FluentValidation; using Incoding.CQRS; using Incoding.Extensions; #endregion public class AddOrEditHumanCommand : CommandBase { #region Properties public DateTime BirthDay { get; set; } public string FirstName { get; set; } public string Id { get; set; } public string LastName { get; set; } public Sex Sex { get; set; } #endregion public override void Execute() { var human = Repository.GetById<Human>(Id) ?? new Human(); human.FirstName = FirstName; human.LastName = LastName; human.Birthday = BirthDay; human.Sex = Sex; Repository.SaveOrUpdate(human); } } }
Read 命令是 CRUD 的第二部分。这是一个从数据库读取实体的请求。添加文件 Example.Domain -> Operations -> GetPeopleQuery.cs。
GetPeopleQuery.cs
namespace Example.Domain { #region << Using >> using System.Collections.Generic; using System.Linq; using Incoding.CQRS; #endregion public class GetPeopleQuery : QueryBase<List<GetPeopleQuery.Response>> { #region Properties public string Keyword { get; set; } #endregion #region Nested Classes public class Response { #region Properties public string Birthday { get; set; } public string FirstName { get; set; } public string Id { get; set; } public string LastName { get; set; } public string Sex { get; set; } #endregion } #endregion protected override List<Response> ExecuteResult() { return Repository.Query<Human>() .Select(human => new Response { Id = human.Id, Birthday = human.Birthday.ToShortDateString(), FirstName = human.FirstName, LastName = human.LastName, Sex = human.Sex.ToString() }) .ToList(); } } }
Delete 命令是 CRUD 的剩余部分。该命令使用键(Id)从数据库中删除记录。添加文件 Example.Domain -> Operations -> DeleteHumanCommand.cs。
DeleteHumanCommand.cs
namespace Example.Domain
{
#region << Using >>
using Incoding.CQRS;
#endregion
public class DeleteHumanCommand : CommandBase
{
#region Properties
public string HumanId { get; set; }
#endregion
public override void Execute()
{
Repository.Delete<Human>(HumanId);
}
}
}
为了用初始数据填充数据库,请添加文件 Example.Domain -> InitPeople.cs,该文件派生自 ISetUP 接口。
ISetup
using System;
namespace Incoding.CQRS
{
public interface ISetUp : IDisposable
{
int GetOrder();
void Execute();
}
}
所有来自 ISetUp 的类实例都在 Bootstrapper.cs(参见简介)中注册到 IoC,并按顺序运行(public void Execute())(public int GetOrder())。
InitPeople.cs
namespace Example.Domain
{
#region << Using >>
using System;
using Incoding.Block.IoC;
using Incoding.CQRS;
using NHibernate.Util;
#endregion
public class InitPeople : ISetUp
{
public void Dispose() { }
public int GetOrder()
{
return 0;
}
public void Execute()
{
//get Dispatcher for execute Query or Command
var dispatcher = IoCFactory.Instance.TryResolve<IDispatcher>();
//don't add new entity if exits
if (dispatcher.Query(new GetEntitiesQuery<Human>()).Any())
return;
//Adding new entity
dispatcher.Push(new AddOrEditHumanCommand
{
FirstName = "Hellen",
LastName = "Jonson",
BirthDay = Convert.ToDateTime("06/05/1985"),
Sex = Sex.Female
});
dispatcher.Push(new AddOrEditHumanCommand
{
FirstName = "John",
LastName = "Carlson",
BirthDay = Convert.ToDateTime("06/07/1985"),
Sex = Sex.Male
});
}
}
}
CRUD 的后端实现已准备就绪。现在是时候添加用户代码了。与后端一样,我们从创建/编辑记录开始实现。将文件 Example.UI -> Views -> Home -> AddOrEditHuman.cshtml 添加到项目中。
AddOrEditHuman.cshtml
@using Example.Domain
@using Incoding.MetaLanguageContrib
@using Incoding.MvcContrib
@model Example.Domain.AddOrEditHumanCommand
@*Submit form for AddOrEditHumanCommand*@
@using (Html.When(JqueryBind.Submit)
@*Prevent default behavior and submit form by Ajax*@
.PreventDefault()
.Submit()
.OnSuccess(dsl =>
{
dsl.WithId("PeopleTable").Core().Trigger.Incoding();
dsl.WithId("dialog").JqueryUI().Dialog.Close();
})
.OnError(dsl => dsl.Self().Core().Form.Validation.Refresh())
.AsHtmlAttributes(new
{
action = Url.Dispatcher().Push(new AddOrEditHumanCommand()),
enctype = "multipart/form-data",
method = "POST"
})
.ToBeginTag(Html, HtmlTag.Form))
{
<div>
@Html.HiddenFor(r => r.Id)
@Html.ForGroup(r => r.FirstName).TextBox(control => control.Label.Name = "First name")
<br/>
@Html.ForGroup(r => r.LastName).TextBox(control => control.Label.Name = "Last name")
<br/>
@Html.ForGroup(r => r.BirthDay).TextBox(control => control.Label.Name = "Birthday")
<br/>
@Html.ForGroup(r => r.Sex).DropDown(control => control.Input.Data = typeof(Sex).ToSelectList())
</div>
<div>
<input type="submit" value="Save"/>
@*Close dialog*@
@(Html.When(JqueryBind.Click)
.PreventDefault()
.StopPropagation()
.Direct()
.OnSuccess(dsl => { dsl.WithId("dialog").JqueryUI().Dialog.Close(); })
.AsHtmlAttributes()
.ToButton("Cancel"))
</div>
}
IML 代码创建标准的 HTML 表单,并与 AddOrEditHumanCommand 交互,向服务器发送相应的 Ajax 请求。然后是用于通过 GetPeopleQuery 加载数据的模板。有一个负责数据输出以及记录删除和编辑的表格描述:将文件 Example.UI -> Views -> Home -> HumanTmpl.cshtml 添加到项目中。
HumanTmpl.cshtml
@using Example.Domain
@using Incoding.MetaLanguageContrib
@using Incoding.MvcContrib
@{
using (var template = Html.Incoding().Template<GetPeopleQuery.Response>())
{
<table class="table">
<thead>
<tr>
<th>
First name
</th>
<th>
Last name
</th>
<th>
Birthday
</th>
<th>
Sex
</th>
<th></th>
</tr>
</thead>
<tbody>
@using (var each = template.ForEach())
{
<tr>
<td>
@each.For(r => r.FirstName)
</td>
<td>
@each.For(r => r.LastName)
</td>
<td>
@each.For(r => r.Birthday)
</td>
<td>
@each.For(r => r.Sex)
</td>
<td>
@*Open edit dialog form*@
@(Html.When(JqueryBind.Click)
.AjaxGet(Url.Dispatcher().Model<AddOrEditHumanCommand>(new
{
Id = each.For(r => r.Id),
FirstName = each.For(r => r.FirstName),
LastName = each.For(r => r.LastName),
BirthDay = each.For(r => r.Birthday),
Sex = each.For(r => r.Sex)
})
.AsView("~/Views/Home/AddOrEditHuman.cshtml"))
.OnSuccess(dsl => dsl.WithId("dialog").Behaviors(inDsl =>
{
inDsl.Core().Insert.Html();
inDsl.JqueryUI().Dialog.Open(option => {
option.Resizable = false;
option.Title = "Edit human";
});
}))
.AsHtmlAttributes()
.ToButton("Edit"))
@*Button delete*@
@(Html.When(JqueryBind.Click)
.AjaxPost(Url.Dispatcher().Push(new DeleteHumanCommand()
{
HumanId = each.For(r => r.Id)
}))
.OnSuccess(dsl => dsl.WithId("PeopleTable").Core().Trigger.Incoding())
.AsHtmlAttributes()
.ToButton("Delete"))
</td>
</tr>
}
</tbody>
</table>
}
}
注意:打开对话框的任务很常见,因此负责此任务的代码可以导出到扩展。因此,只需要更改起始页,以便在加载时将 AJAX 请求发送到服务器以从 GetPeopleQuery 获取数据并使用 HumanTmpl 映射数据:将文件 Example.UI -> Views -> Home -> Index.cshtml 更改为如下所示。
Index.cshtml
@using Example.Domain
@using Incoding.MetaLanguageContrib
@using Incoding.MvcContrib
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div id="dialog"></div>
@*Fetch data from GetPeopleQuery, through HumanTmpl*@
@(Html.When(JqueryBind.InitIncoding)
.AjaxGet(Url.Dispatcher().Query(new GetPeopleQuery()).AsJson())
.OnSuccess(dsl =>
dsl.Self().Core()
.Insert.WithTemplateByUrl(Url.Dispatcher().AsView("~/Views/Home/HumanTmpl.cshtml")).Html())
.AsHtmlAttributes(new { id = "PeopleTable" })
.ToDiv())
@*Button add*@
@(Html.When(JqueryBind.Click)
.AjaxGet(Url.Dispatcher().AsView("~/Views/Home/AddOrEditHuman.cshtml"))
.OnSuccess(dsl => dsl.WithId("dialog").Behaviors(inDsl =>
{
inDsl.Core().Insert.Html();
inDsl.JqueryUI().Dialog.Open(option =>
{
option.Resizable = false;
option.Title = "Add human";
});
}))
.AsHtmlAttributes()
.ToButton("Add new human"))
在实际应用程序中,输入表单数据的验证是最常见的任务之一。因此,我们在 Human 实体的添加/编辑表单上添加数据验证。首先,我们需要添加服务器代码。将以下代码作为嵌套类添加到 AddOrEditHumanCommand 中。
#region Nested Classes public class Validator : AbstractValidator { #region Constructors public Validator() { RuleFor(r => r.FirstName).NotEmpty(); RuleFor(r => r.LastName).NotEmpty(); } #endregion } #endregion
在 AddOrEditHuman.cshtml 表单上,我们使用了如下构造:
@Html.ForGroup()
因此,不必添加
@Html.ValidationMessageFor()
对于字段 - ForGroup() 就能完成。
因此,我们已经编写了实现一个数据库实体 CRUD 功能的应用程序代码。
第 4 部分。Specifications - 数据清理。
在实际项目中经常出现的另一个任务是清理请求的数据。Incoding Framework 使用 WhereSpecifications 来方便地编写代码并遵守封装原则,以从 Query 中清理数据。在编写的代码中,添加一种方式来清理 GetPeopleQuery 中按 FirstName 和 LastName 清理的数据。首先,添加两个规范文件 Example.Domain -> Specifications -> HumanByFirstNameWhereSpec.cs 和 Example.UI -> Specifications -> HumanByLastNameWhereSpec.cs。
HumanByFirstNameWhereSpec.cs
namespace Example.Domain
{
#region << Using >>
using System;
using System.Linq.Expressions;
using Incoding;
#endregion
public class HumanByFirstNameWhereSpec : Specification
{
#region Fields
readonly string firstName;
#endregion
#region Constructors
public HumanByFirstNameWhereSpec(string firstName)
{
this.firstName = firstName;
}
#endregion
public override Expression<Func<Human, bool>> IsSatisfiedBy()
{
if (string.IsNullOrEmpty(this.firstName))
return null;
return human => human.FirstName.ToLower().Contains(this.firstName.ToLower());
}
}
}
HumanByLastNameWhereSpec.cs
namespace Example.Domain { #region << Using >> using System; using System.Linq.Expressions; using Incoding; #endregion public class HumanByLastNameWhereSpec : Specification { #region Fields readonly string lastName; #endregion #region Constructors public HumanByLastNameWhereSpec(string lastName) { this.lastName = lastName.ToLower(); } #endregion public override Expression<Func<Human, bool>> IsSatisfiedBy() { if (string.IsNullOrEmpty(this.lastName)) return null; return human => human.LastName.ToLower().Contains(this.lastName); } } }
现在,在 GetPeopleQuery 中使用编写的规范。.Or()/.And() 关系允许合并原子规范,这有助于多次使用创建的规范并微调必要的数据过滤器(在本例中我们使用 .Or() 关系)。
GetPeopleQuery.cs
namespace Example.Domain { #region << Using >> using System.Collections.Generic; using System.Linq; using Incoding.CQRS; using Incoding.Extensions; #endregion public class GetPeopleQuery : QueryBase<List<GetPeopleQuery.Response>> { #region Properties public string Keyword { get; set; } #endregion #region Nested Classes public class Response { #region Properties public string Birthday { get; set; } public string FirstName { get; set; } public string Id { get; set; } public string LastName { get; set; } public string Sex { get; set; } #endregion } #endregion protected override List<Response> ExecuteResult() { return Repository.Query(whereSpecification:new HumanByFirstNameWhereSpec(Keyword) .Or(new HumanByLastNameWhereSpec(Keyword))) .Select(human => new Response { Id = human.Id, Birthday = human.Birthday.ToShortDateString(), FirstName = human.FirstName, LastName = human.LastName, Sex = human.Sex.ToString() }) .ToList(); } } }
最后,只需要修改 Index.cshtml 以添加一个搜索框,该搜索框在处理请求时使用 Keyword 字段进行数据清理。
Index.cshtml
@using Example.Domain @using Incoding.MetaLanguageContrib @using Incoding.MvcContrib @{ Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="dialog"></div>
@*Then click Find event InitIncoding and PeopleTable calling GetPeopleQuery with parameter Keyword*@ <div> <input type="text" id="Keyword"/> @(Html.When(JqueryBind.Click) .Direct() .OnSuccess(dsl => dsl.WithId("PeopleTable").Core().Trigger.Incoding()) .AsHtmlAttributes() .ToButton("Find")) </div> @(Html.When(JqueryBind.InitIncoding) .AjaxGet(Url.Dispatcher().Query(new GetPeopleQuery { Keyword = Selector.Jquery.Id("Keyword") }).AsJson()) .OnSuccess(dsl => dsl.Self().Core().Insert.WithTemplateByUrl(Url.Dispatcher().AsView("~/Views/Home/HumanTmpl.cshtml")).Html()) .AsHtmlAttributes(new { id = "PeopleTable" }) .ToDiv()) @(Html.When(JqueryBind.Click) .AjaxGet(Url.Dispatcher().AsView("~/Views/Home/AddOrEditHuman.cshtml")) .OnSuccess(dsl => dsl.WithId("dialog").Behaviors(inDsl => { inDsl.Core().Insert.Html(); inDsl.JqueryUI().Dialog.Open(option => { option.Resizable = false; option.Title = "Add human"; }); })) .AsHtmlAttributes() .ToButton("Add new human"))
第 5 部分。单元测试。
让我们用测试来覆盖已编写的代码。第一个负责测试 Human 实体映射。将文件 When_save_Human.cs 添加到 UnitTests 项目的 Persisteces 文件夹中。
When_save_Human.cs
namespace Example.UnitTests.Persistences { #region << Using >> using Example.Domain; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(Human))] public class When_save_Human : SpecWithPersistenceSpecification { #region Fields It should_be_verify = () => persistenceSpecification.VerifyMappingAndSchema(); #endregion } }
测试使用测试数据库(Example_test):创建一个 Human 类的实例,并自动填充字段,然后将其存储在数据库中,从数据库中检索出来,并与创建的实例进行比较。然后,在名为 Specifications 的文件夹中添加 WhereSpecifications 的测试。
When_human_by_first_name.cs
namespace Example.UnitTests.Specifications { #region << Using >> using System; using System.Collections.Generic; using System.Linq; using Example.Domain; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(HumanByFirstNameWhereSpec))] public class When_human_by_first_name { #region Fields Establish establish = () => { Func<string, Human> createEntity = (firstName) => Pleasure.MockStrictAsObject(mock => mock.SetupGet(r => r.FirstName).Returns(firstName)); fakeCollection = Pleasure.ToQueryable(createEntity(Pleasure.Generator.TheSameString()), createEntity(Pleasure.Generator.String())); }; Because of = () => { filterCollection = fakeCollection .Where(new HumanByFirstNameWhereSpec(Pleasure.Generator.TheSameString()).IsSatisfiedBy()) .ToList(); }; It should_be_filter = () => { filterCollection.Count.ShouldEqual(1); filterCollection[0].FirstName.ShouldBeTheSameString(); }; #endregion #region Establish value static IQueryable fakeCollection; static List filterCollection; #endregion } }
When_human_by_last_name.cs
namespace Example.UnitTests.Specifications { #region << Using >> using System; using System.Collections.Generic; using System.Linq; using Example.Domain; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(HumanByLastNameWhereSpec))] public class When_human_by_last_name { #region Fields Establish establish = () => { Func<string, Human> createEntity = (lastName) => Pleasure.MockStrictAsObject(mock =>mock.SetupGet(r => r.LastName).Returns(lastName)); fakeCollection = Pleasure.ToQueryable(createEntity(Pleasure.Generator.TheSameString()), createEntity(Pleasure.Generator.String())); }; Because of = () => { filterCollection = fakeCollection .Where(new HumanByLastNameWhereSpec(Pleasure.Generator.TheSameString()).IsSatisfiedBy()) .ToList(); }; It should_be_filter = () => { filterCollection.Count.ShouldEqual(1); filterCollection[0].LastName.ShouldBeTheSameString(); }; #endregion #region Establish value static IQueryable fakeCollection; static List filterCollection; #endregion } }
现在,我们必须为命令和查询(Operations 文件夹)添加测试。对于命令,您需要添加两个测试:第一个测试创建新实体;第二个测试编辑现有实体。
When_get_people_query.cs
namespace Example.UnitTests.Operations { #region << Using >> using System.Collections.Generic; using Example.Domain; using Incoding.Extensions; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(GetPeopleQuery))] public class When_get_people { #region Fields Establish establish = () => { var query = Pleasure.Generator.Invent<GetPeopleQuery>(); //Create entity for test with auto-generate human = Pleasure.Generator.Invent<Human>(); expected = new List<GetPeopleQuery.Response>(); mockQuery = MockQuery<GetPeopleQuery, List<GetPeopleQuery.Response>> .When(query) //"Stub" on query to repository .StubQuery(whereSpecification: new HumanByFirstNameWhereSpec(query.Keyword) .Or(new HumanByLastNameWhereSpec(query.Keyword)), entities: human); }; Because of = () => mockQuery.Original.Execute(); // Compare result It should_be_result = () => mockQuery .ShouldBeIsResult(list => list.ShouldEqualWeakEach(new List<Human>() { human }, (dsl, i) => dsl.ForwardToValue(r => r.Birthday, human.Birthday.ToShortDateString()) .ForwardToValue(r => r.Sex, human.Sex.ToString()))); #endregion #region Establish value static MockMessage<GetPeopleQuery, List<GetPeopleQuery.Response>> mockQuery; static List<GetPeopleQuery.Response> expected; static Human human; #endregion } }
When_add_human.cs
namespace Example.UnitTests.Operations { #region << Using >> using Example.Domain; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(AddOrEditHumanCommand))] public class When_add_human { #region Fields Establish establish = () => { var command = Pleasure.Generator.Invent<AddOrEditHumanCommand>(); mockCommand = MockCommand<AddOrEditHumanCommand> .When(command) //"Stub" on repository .StubGetById<Human>(command.Id, null); }; Because of = () => mockCommand.Original.Execute(); It should_be_saved = () => mockCommand .ShouldBeSaveOrUpdate<Human>(human => human.ShouldEqualWeak(mockCommand.Original)); #endregion #region Establish value static MockMessage<AddOrEditHumanCommand, object> mockCommand; #endregion } }
When_edit_human.cs
namespace Example.UnitTests.Operations { #region << Using >> using Example.Domain; using Incoding.MSpecContrib; using Machine.Specifications; #endregion [Subject(typeof(AddOrEditHumanCommand))] public class When_edit_human { #region Fields Establish establish = () => { var command = Pleasure.Generator.Invent<AddOrEditHumanCommand>(); human = Pleasure.Generator.Invent<Human>(); mockCommand = MockCommand<AddOrEditHumanCommand> .When(command) //"Stub" on repository .StubGetById(command.Id, human); }; Because of = () => mockCommand.Original.Execute(); It should_be_saved = () => mockCommand .ShouldBeSaveOrUpdate<Human>(human => human.ShouldEqualWeak(mockCommand.Original)); #endregion #region Establish value static MockMessage<AddOrEditHumanCommand, object> mockCommand; static Human human; #endregion } }
学习资料
- CQRS 和 CQRS (高级课程), Repository - 后端架构。
- MVD - Model View Dispatcher 模式的描述。
- IML (TODO),IML vs Angular,IML vs Jquery,IML vs ASP.NET Ajax - incoding 元语言。
- IML(选择器)- 描述了 IML 中选择器的用法。
- IML In Ajax - 描述了 IML 操作与 Ajax 的关系。
- IML 模板- 用于数据插入的模板。
- Extensions - 帮助编写扩展以遵守Don'tRepeatYourself原则。
- Unit Test 和 Unit test scenario。