📜 ⬆️ ⬇️

Incoding Framework - Get started

IncFramework-logo

disclaimer: This article is a step-by-step tutorial that will help you become familiar with the main features of the Incoding Framework . Following this guide will result in a unit-covered application that implements working with the database (CRUD + data filters). About the Incoding framework, there have already been articles on habrahabr, but they reveal separate parts of the tool.

Part 0. Introduction


To begin, we give a brief description of the framework . Incoding Framework consists of three packages: Incoding framework - the back-end of the project, Incoding Meta Language - the front-end of the project and Incoding tests helpers - unit tests for the back-end. These packages are installed independently of each other, which allows you to integrate the framework into the project in parts: you can connect only the client or only the server part (the tests are very strongly connected with the server part, so they can be positioned as an addition).
In projects written in the Incoding Framework ,   CQRS is used as the server architecture. Incoding Meta Language is used as the main tool for building the client part. In general, the Incoding Framework covers the entire application development cycle.
A typical solution created using the Incoding Framework has 3 projects:
')
  1. Domain ( class library) - is responsible for business logic and work with the database.
  2. UI ( ASP.NET MVC project )   - client part based on ASP.NET MVC.
  3. UnitTests ( class library ) - unit tests for Domain.


Domain


After installing the Incoding framework package via Nuget, in addition to the necessary dlls, the Bootstrapper.cs file is installed. The main task of this file is to initialize the application: initialize logging, register IoC, set up Ajax requests, etc. The StructureMap is set as the IoC framework by default, but there is a provider for Ninject, and you can also write your implementations.

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

Next, in the Domain, commands (Command) and queries (Query) are added that perform operations with the database or some other actions related to the business logic of the application.

Ui


The Incoding Meta Language package during installation adds the necessary dll to the project, as well as the IncodingStart.cs and DispatcherController.cs files (part of the MVD ) necessary for working with Domain.

 public static class IncodingStart { public static void source Start() { Bootstrapper.Start(); new DispatcherController(); // init routes } } 

 public class DispatcherController : DispatcherControllerBase { #region Constructors public DispatcherController() : base(typeof(Bootstrapper).Assembly) { } #endregion } 

After installation, client logic is added to the UI using IML .

UnitTests


When you install the Incoding tests helpers, the MSpecAssemblyContext.cs file is added to the project, which configures the connection to the test database.

 public class MSpecAssemblyContext : IAssemblyContext { #region IAssemblyContext Members public void OnAssemblyStart() { //     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 } 


Part 1. Installation


So, we will start implementation of the task set in the disclamer - we will start writing our application. The first step in creating an application is to create the structure of the project’s solution and add projects to it. The project's solution will be named Example and, as already mentioned in the introduction, will have three projects. Let's start with the project, which will be responsible for the business logic of the application - with Domain.
We create class library Domain .
Domain
Next, let's move on to the client side — create and install an ASP.NET Web Application UI empty project with links to MVC packages.
UI1

UI2
Finally, add the class library UnitTests, which is responsible for unit testing.
UnitTests
Attention: although unit tests are not a mandatory part of the application, we recommend that you always cover the code with tests, as this will allow you to avoid many problems with errors in the code by automating testing in the future.

After performing all the above actions, you should get the following Solution:
Solution
After creating the Solution structure, you must install the Incoding Framework packages from Nuget into the appropriate projects.
Installation takes place through Nuget. For all projects, the installation algorithm is one:

  1. Right-click on the project and select Manage Nuget Packages in the context menu ...
  2. In the search enter incoding
  3. Select the package you need and install it.

First install the Incoding framework in Domain .
Incoding_framework_1
Next, add a link to StructureMap.Graph to the Domain -> Infrastructure -> Bootstrapper.cs file.
StructureMap_ref

You need to install two packages in the UI :
  1. Incoding Meta Language
  2. Incoding Meta Language Contrib

Incoding_Meta_Languge
MetaLanguageContrib_install
Attention: make sure that for References -> System.Web.Mvc.dll property “Copy Local” is set to “true”
Now, modify the file Example.UI -> Views -> Shared -> _Layout.cshtml so that it looks like this:

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

It remains to add a link to Bootstrapper.cs in the files Example.UI -> App_Start -> IncodingStart.cs and Example.UI -> Controllers -> DispatcherController.cs .
IncodingStart_bootstrapper

DispatcherController_bootstrapper
Note: if you are using MVC5, then for the framework to work, you need to add the following code to the Web.config file

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

It remains to install the Incoding tests helpers in UnitTests and add a link to Bootstrapper.cs in Example.UnitTests -> MSpecAssemblyContext.cs.
Incoding_tests_helpers

MSpecAssemblyContext_bootstrapper
The last stage of preparing projects for work is to create a folder structure for projects.
Add the following folders to the Example.Domain project :

  1. Operations - command and query project
  2. Persistences - entities for database mapping
  3. Specifications - where and order specifications for filtering data on queries

Example.Domain_folders
In the Example.UnitTests project , create the same folder structure as in Example.Domain.
UnitTests_folders

Part 2. Setting up a DB connection


To begin, create a database with which we will work. Open SQL Managment Studio and create two databases: Example and Example_test.
add_DB
example_db
example_test_db
In order to work with the database you need to configure the connection. Add to the files Example.UI -> Web.config and Example.UnitTests -> app.config connection string to the database:

  <connectionStrings> <add name="Example" connectionString="Data Source=INCODING-PC\SQLEXsource SS;Database=Example;Integrated Security=false; User Id=sa;Password=1" providerName="System.Data.SqlClient" /> <add name="Example_Test" connectionString="Data Source=INCODING-PC\SQLEXsource SS;Database=Example_Test;Integrated Security=true" providerName="System.Data.SqlClient" /> </connectionStrings> 

In the Example.Domain -> Infrastructure -> Bootstrapper.cs file, register the corresponding connection string using the "Example" key:

 //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 

In the file Example.UnitTests -> MSpecAssemblyContext.cs, register the test database connection string using the "Example_Test" key:

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

Note: the Example and Example_Test databases must exist.

Part 3. CRUD


After performing all the above steps, we come to the most interesting part - writing code that implements the C reate R ead U pdate D elete-functional application. First you need to create an entity class that will map to the database. In our case, this will be Human.cs, which we add to the Example.Domain -> Persistences folder .

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

Our class contains several fields in which we will write data, and a nested mapping class ( class Map).
Note: after creating the Human class, you no longer need to perform any actions (adding xml mapping) thanks to FluentNhibernate .
Now add commands (Command) and queries (Query), which will be responsible for the implementation of CRUD operations. The first command will be responsible for adding a new or modifying an existing Human record. The command is quite simple: we either get an entity from the Repository by key (Id), or, if there is no such entity, we create a new one. In both cases, the entity receives the values ​​that are specified in the properties of the AddOrEditHumanCommand class. Add the Example.Domain -> Operations -> AddOrEditHumanCommand.cs file to the project.

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

The next part of CRUD - Read is a request to read entities from the database. Add the Example.Domain -> Operations -> GetPeopleQuery.cs file.

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

And the rest of the functionality is Delete - deleting records from the database by key (Id). Add the Example.Domain file -> 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); } } } 

In order to fill the database with initial data, add the file Example.Domain -> InitPeople.cs - this file is inherited from the ISetUp interface.

ISetup

 using System; namespace Incoding.CQRS { public interface ISetUp : IDisposable { int GetOrder(); void Execute(); } } 

All instances of classes inherited from ISetUp are registered via IoC in Bootstrapper.cs (was given in the introduction). After registration, they are launched for execution (public void Execute ()) in order (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() { // Dispatcher   Query  Command var dispatcher = IoCFactory.Instance.TryResolve<IDispatcher>(); //  ,         if (dispatcher.Query(new GetEntitiesQuery<Human>()).Any()) return; //  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 }); } } } 

Back-end CRUD implementation is ready. Now you need to add client code. As in the case of the server part, let's start the implementation from the part of creating / editing a record. Add the Example.UI file -> Views -> Home -> AddOrEditHuman.cshtml. The presented IML code creates a standard html form and works with the AddOrEditHumanCommand command, sending the corresponding Ajax request to the server.

AddOrEditHuman.cshtml

 @using Example.Domain @using Incoding.MetaLanguageContrib @using Incoding.MvcContrib @model Example.Domain.AddOrEditHumanCommand @*   Ajax-   AddOrEditHumanCommand*@ @using (Html.When(JqueryBind.Submit) @*        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"/> @* *@ @(Html.When(JqueryBind.Click) .PreventDefault() .StopPropagation() .Direct() .OnSuccess(dsl => { dsl.WithId("dialog").JqueryUI().Dialog.Close(); }) .AsHtmlAttributes() .ToButton("Cancel")) </div> } 

This is followed by the template, which is a template for loading data received from GetPeopleQuery. It describes a table that will be responsible not only for data output, but also for deleting and editing individual records: add the file 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> @*    *@ @(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")) @*  *@ @(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> } } 

The task of opening a dialog box is quite common, so the code responsible for this action can be transferred to an extension .

The last part is changing the start page so that when it is loaded, an Ajax request is sent to the server to get data from GetPeopleQuery and display it through HumanTmpl: change the Example.UI file -> Views -> Home -> Index.cshtml so that it matches the code below.
Index.cshtml

 @using Example.Domain @using Incoding.MetaLanguageContrib @using Incoding.MvcContrib @{ Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="dialog"></div> @* ,   GetPeopleQuery,  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()) @*   *@ @(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")) 

In real-world applications, validation of entered form data is one of the most frequent tasks. Therefore, we will add data validation to the Human entity add / edit form. The first part is adding server code.   Add the following code in AddOrEditHumanCommand as the nested class:

 #region Nested Classes public class Validator : AbstractValidator { #region Constructors public Validator() { RuleFor(r => r.FirstName).NotEmpty(); RuleFor(r => r.LastName).NotEmpty(); } #endregion } #endregion 

On the form AddOrEditHuman.cshtml we used constructions of the form: <

 @Html.ForGroup() 

Therefore, there is no need to add

 @Html.ValidationMessageFor() 

for fields - ForGroup () will do it for us.
Thus, we have written application code that implements the CRUD functionality for a single database entity.

Part 4. Specifications - data filtering


Another of the tasks that are often found in real projects is filtering the requested data. In the Incoding Framework, for the convenience of writing code and following the principle of encapsulation, WhereSpecifications are used to filter the data obtained in Query. Let's add to the written code the ability to filter the data received in GetPeopleQuery by FirstName and LastName. First, we add two specification.Remain -> Specifications -> HumanByFirstNameWhereSpec.cs and Example.UI -> Specifications -> HumanByLastNameWhereSpec.cs specification files.

HumanByFirstNameWhereSpec.cs

 namespace Example.Domain { #region << Using >> using System; using System.Linq.Exsource ssions; 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 Exsource ssion<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.Exsource ssions; 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 Exsource ssion<Func<Human, bool>> IsSatisfiedBy() { if (string.IsNullOrEmpty(this.lastName)) return null; return human => human.LastName.ToLower().Contains(this.lastName); } } } 

Now we use the written specifications in the GetPeopleQuery request. With the help of the bundles. Or () /. And (), atomic specifications can be combined into more complex ones, which helps to use created specifications many times and with their help fine-tune the necessary data filters (in our example, we use the bundle. 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(); } } } 


Finally, it remains to modify Index.cshtml a bit to add a search string that, when requested, includes a Keyword field for filtering data.

Index.cshtml

 @using Example.Domain @using Incoding.MetaLanguageContrib @using Incoding.MvcContrib @{ Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="dialog"></div> @*   Find   InitIncoding  PeopleTable   GetPeopleQuery   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")) 

Part 5. Unit tests.


We will cut the written code with tests. The first test is responsible for checking the mapping of the Human entity. The When_save_Human.cs file will be added to the Persisteces folder of the UnitTests project.

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

This test works with a test database (Example_test): it creates an instance of the Human class with automatically filled fields, saves it to the database, and then retrieves and compares it with the created instance.
Now add the tests for WhereSpecifications to the Specifications folder.

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

Now it remains to add tests for the team and the query (Operations folder), and for the team you need to add two tests: one to check the creation of a new entity and the second to check the editing of an existing entity.

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

List of materials for study


  1. CQRS - server architecture
  2. MVD — Description of Model View Dispatcher Pattern
  3. Iml introduction
  4. IML TODO MVC

Source: https://habr.com/ru/post/266609/


All Articles