Incoding Framework - Getting Started

IncFramework-logo

Figure 1: Incoding

Part 0: Introduction

Let us begin with a short description of Framework. Incoding framework comprises three packages: Incoding framework – back-end project, Incoding Meta Language – front-end project and Incoding tests helpers – unit-tests for back-end. These packages are installed independently of each other, making it possible to integrate framework by parts into the project: You can connect only front or back end (tests are tightly coupled with the back end, so, they could be more considered as a complement).

Projects developed in Incoding Framework, use CQRS as a server architecture. Incoding Meta Language. Incoding Framework is used as a basic tool for building front-end. All in all, Incoding Framework covers the entire application development cycle.

Typical solution, that was developed using Incoding Framework, comprises the following three projects:

  1. Domain (class library) - is responsible for business logic and database operations.
  2. UI (ASP.NET MVC project) - front-end based on ASP.NET MVC.
  3. UnitTests (class library) - unit-tests for Domain.

Domain

After installation of Incoding framework through Nuget, along with the necessary dll, Bootstrapper.cs file will be added to the project. The file is mainly responsible for the initialization of an application: logging initialization, IoC registration, installation of Ajax-requests settings, etc. By default, StructureMap is installed as loC framework, but there is a provider for Ninject, and it is also possible to write your own implementations.

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.     using System;  
  5.     using System.Configuration;  
  6.     using System.IO;  
  7.     using System.Linq;  
  8.     using System.Web.Mvc;  
  9.     using FluentNHibernate.Cfg;  
  10.     using FluentNHibernate.Cfg.Db;  
  11.     using FluentValidation;  
  12.     using FluentValidation.Mvc;  
  13.     using Incoding.Block.IoC;  
  14.     using Incoding.Block.Logging;  
  15.     using Incoding.CQRS;  
  16.     using Incoding.Data;  
  17.     using Incoding.EventBroker;  
  18.     using Incoding.Extensions;  
  19.     using Incoding.MvcContrib;  
  20.     using NHibernate.Tool.hbm2ddl;  
  21.     using StructureMap.Graph;  
  22.     #endregion  
  23.     public static class Bootstrapper  
  24.     {  
  25.         public static void Start()  
  26.         {  
  27.             //Initialize LoggingFactory  
  28.             LoggingFactory.Instance.Initialize(logging =>  
  29.                 {  
  30.                     string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");  
  31.                     logging.WithPolicy(policy => policy.For(LogType.Debug)  
  32.                                                         .Use(FileLogger.WithAtOnceReplace(path,  
  33.                                                                                         () => "Debug_{0}.txt".F(DateTime.Now.ToString("yyyyMMdd")))));  
  34.                 });  
  35.               //Initialize IoCFactory  
  36.             IoCFactory.Instance.Initialize(init => init.WithProvider(new StructureMapIoCProvider(registry =>  
  37.                 {                   
  38.                     registry.For<IDispatcher>().Use<DefaultDispatcher>();              
  39.                     registry.For<IEventBroker>().Use<DefaultEventBroker>();  
  40.                     registry.For<ITemplateFactory>().Singleton().Use<TemplateHandlebarsFactory>();  
  41.                       //Configure FluentlyNhibernate  
  42.                     var configure = Fluently  
  43.                             .Configure()  
  44.                             .Database(MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager.ConnectionStrings["Example"].ConnectionString))  
  45.                             .Mappings(configuration => configuration.FluentMappings.AddFromAssembly(typeof(Bootstrapper).Assembly))  
  46.                             .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(falsetrue))  
  47.                             .CurrentSessionContext<NhibernateSessionContext>(); //Настройка конфигурации базы данных  
  48.   
  49.                     registry.For<INhibernateSessionFactory>().Singleton().Use(() => new NhibernateSessionFactory(configure));  
  50.                     registry.For<IUnitOfWorkFactory>().Use<NhibernateUnitOfWorkFactory>();  
  51.                     registry.For<IRepository>().Use<NhibernateRepository>();  
  52.                       //Scna currenlty Assembly and registrations all Validators and Event Subscribers  
  53.                     registry.Scan(r =>  
  54.                                     {  
  55.                                         r.TheCallingAssembly();  
  56.                                         r.WithDefaultConventions();  
  57.                                         r.ConnectImplementationsToTypesClosing(typeof(AbstractValidator<>));  
  58.                                         r.ConnectImplementationsToTypesClosing(typeof(IEventSubscriber<>));  
  59.                                         r.AddAllTypesOf<ISetUp>();  
  60.                                     });  
  61.                 })));  
  62.             ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(new IncValidatorFactory()));  
  63.             FluentValidationModelValidatorProvider.Configure();  
  64.             //Execute all SetUp  
  65.             foreach (var setUp in IoCFactory.Instance.ResolveAll<ISetUp>().OrderBy(r => r.GetOrder()))  
  66.             {  
  67.                 setUp.Execute();  
  68.             }  
  69.             var ajaxDef = JqueryAjaxOptions.Default;  
  70.             ajaxDef.Cache = false//Disable Ajax cache  
  71.         }  
  72.     }  
  73. }     

Further on, commands (Command) and queries (Query) are added to the domain, that perform database operations or any action, related with business application logic.

UI

During the installation of package Incoding Meta Language , it adds the necessary dll to the package, as well as IncodingStart.cs and DispatcherController.cs (part MVD) files required to work Domain.

  1. public class DispatcherController : DispatcherControllerBase  
  2. {  
  3.     #region Constructors  
  4.     public DispatcherController()  
  5.             : base(typeof(Bootstrapper).Assembly) { }  
  6.     #endregion  
  7. }  

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

UnitTests

During the installation of Incoding tests helpers, the project is added by the MSpecAssemblyContext.csis file, in which connection is customized to the test database.

  1. public class MSpecAssemblyContext : IAssemblyContext  
  2. {  
  3.     #region IAssemblyContext Members  
  4.   
  5.     public void OnAssemblyStart()  
  6.     {  
  7.         //Configuration data base  
  8.         var configure = Fluently  
  9.                 .Configure()  
  10.                 .Database(MsSqlConfiguration.MsSql2008  
  11.                                             .ConnectionString(ConfigurationManager.ConnectionStrings["Example_Test"].ConnectionString)  
  12.                                             .ShowSql())  
  13.                 .Mappings(configuration => configuration.FluentMappings.AddFromAssembly(typeof(Bootstrapper).Assembly));  
  14.         PleasureForData.StartNhibernate(configure, true);  
  15.     }  
  16.   
  17.     public void OnAssemblyComplete() { }  
  18.  
  19.     #endregion  
  20. }  

Part 1: Installation

So, we proceed to the task of the disclaimer and start writing our application. The first phase of building the application is to create solution structure of a project and to add the projects to it. The project solution will be called Example and as was already mentioned in the introduction, will have three projects. We begin with the project that is responsible for business logic of the application - Domain.

Create class library Domain.

Domain

Figure 2: Library

Then we proceed to the front-end and create, install ASP.NET Web Application UI with links to the MVC packages as template, empty project.

UI1

Figure 3: Empty Project

UI2

Figure 4: MVC App

Finally, we added class library UnitTests responsible for unit testing.

UnitTests

Figure 5: Unit Testing

Note: Although UnitTests are not an obligatory part of the application, we recommend you to cover the code with tests as it will help to avoid numerous problems in future with various possible faults in the code due to test automation.

After having finished all the above activities, you will get the following solution.

Solution

Figure 6: Solution
 
After we create the solution structure, we need to install Incoding Framework package from Nuget. The installation carried out by Nuget.
Here is the same algorithm of installation for all the projects.
  1. Right-click the project and select Manage NuGet Packages… in the context menu
  2. Search incoding
  3. Select necessary package and install it

First install Incoding framework in Domain

Incoding_framework_1

Figure 7: Framework Domain

Then add to the file Domain, Infrastructure, Bootstrapper.cs link to StructureMap.Graph.

StructureMap_ref

Figure 8: Structure MAp Graph

2 packages must be installed to UI:
  1. Incoding Meta Language
  2. Incoding Meta Language Contrib (optional)

Incoding_Meta_Languge

Figure 9: Incoding Meta

MetaLanguageContrib_install

Figure 10: ExampleUI

Note: Make sure that the Copy Local property is set to true in the References -> System.Web.Mvc.dll

Now change the file Example.UI - Go to Views, Shared, then _Layout.cshtml so that it looks as in the following:

  1. @using Incoding.MvcContrib  
  2. <!DOCTYPE html>  
  3. <html >  
  4. <head>  
  5.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"> </script>  
  6.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-ui-1.10.2.min.js")"></script>  
  7.     <script type="text/javascript" src="@Url.Content("~/Scripts/underscore.min.js")"> </script>  
  8.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.form.min.js")"> </script>  
  9.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.history.js")"> </script>  
  10.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.min.js")"> </script>  
  11.     <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"> </script>  
  12.     <script type="text/javascript" src="@Url.Content("~/Scripts/handlebars-1.1.2.js")"> </script>  
  13.     <script type="text/javascript" src="@Url.Content("~/Scripts/incoding.framework.min.js")"> </script>  
  14.     <script type="text/javascript" src="@Url.Content("~/Scripts/incoding.meta.language.contrib.js")"> </script>  
  15.     <script type="text/javascript" src="@Url.Content("~/Scripts/bootstrap.min.js")"> </script>  
  16.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/bootstrap.min.css")">  
  17.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.core.css")">  
  18.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.datepicker.css")">  
  19.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.dialog.css")">  
  20.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.theme.css")">  
  21.     <link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery.ui.menu.css")">  
  22.     <script>  
  23.         TemplateFactory.Version = '@Guid.NewGuid().ToString()';  
  24.     </script>  
  25. </head>  
  26. @Html.Incoding().RenderDropDownTemplate()  
  27. <body>  
  28. @RenderBody()  
  29. </body>  
  30. </html>  

Then add the link to Bootstrapper.cs to the files Example.UI, go to App_Start, IncodingStart.cs and select Example.UI, then click Controllers and DispatcherController.cs.

IncodingStart_bootstrapper

Figure 11: Bootstrapper

DispatcherController_bootstrapper

Figure 12: Coding

Note: If you use MVC5, it’s necessary for framework to add the following code to Web.config file.

  1. <dependentAssembly>  
  2.   <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />  
  3.   <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />  
  4. </dependentAssembly>  

Now install Incoding tests helpers in UnitTests and add the link to Bootstrapper.cs in Example.UnitTests, MSpecAssemblyContext.cs.

Incoding_tests_helpers

Figure 13: Incoding Test

MSpecAssemblyContext_bootstrapper

Figure 14: Output

The last phase of the preparing the projects to work is to create folders structure for the projects. Add the following folders to the Example.Domain project:
  1. Operations – command and query of the project.
  2. Persistence – entities for DB mapping.
  3. Specifications – where and order specifications for data cleaning when request is made.

Example.Domain_folders 

Figure 15: Opretions

In the Example.UnitTests project create just the same folders structure as in Example.Domain.
 
UnitTests_folders

Figure 16: Example Unit Test
 
Part 2: Setting up a DB connection

To begin this process, create DB with which you will work. Open SQL Managment Studio and create two DB: Example and Example_test.

add_DB

Figure 17: Database 

example_db

Figure 18: New Database 

example_test_db

Figure 19: Create Table 

In order to work with DB, you need to set up a connection. Add to the file Example.UI, then Web.config and Example.UnitTests, then app.config connection string to the BD.

  1. <connectionStrings>  
  2.   <add name="Example" connectionString="Data Source=INCODING-PC\SQLEXPRESS;Database=Example;Integrated Security=false; User Id=sa;Password=1" providerName="System.Data.SqlClient" />  
  3.   <add name="Example_Test" connectionString="Data Source=INCODING-PC\SQLEXPRESS;Database=Example_Test;Integrated Security=true" providerName="System.Data.SqlClient" />  
  4. </connectionStrings>  
In the file Example.Domain, go to Infrastructure and click Bootstrapper.cs, register the appropriate connection string using a key called Example.
  1. //Настройка FluentlyNhibernate  
  2. var configure = Fluently  
  3.         .Configure()  
  4.         .Database(MsSqlConfiguration.MsSql2008.ConnectionString(ConfigurationManager.ConnectionStrings["Example"].ConnectionString))  
  5.         .Mappings(configuration => configuration.FluentMappings.AddFromAssembly(typeof(Bootstrapper).Assembly))  
  6.         .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(falsetrue))  
  7.         .CurrentSessionContext(); //Настройка конфигурации базы данных  
 In the file Example.UnitTests -> MSpecAssemblyContext.cs, register the connection string to the BD using the key called Example_test.
  1. //Настройка подключения к тестовой БД  
  2. var configure = Fluently  
  3.         .Configure()  
  4.         .Database(MsSqlConfiguration.MsSql2008  
  5.                                     .ConnectionString(ConfigurationManager.ConnectionStrings["Example_Test"].ConnectionString)  
  6.                                     .ShowSql())  
  7.         .Mappings(configuration => configuration.FluentMappings.AddFromAssembly(typeof(Bootstrapper).Assembly));  

Note: Example and Example_test databases must exist.

Part 3: CRUD

After the actions described above, we come to the most interesting part – code writing implementing the CRUD (create, read, update, delete) functionality of an application. To begin the process, create an entity class that will map to the DB. In our case, this is Human.cs that we add to the Example.Domain -> Persistences folder.

Human.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using Incoding.Data;  
  7.  
  8.     #endregion  
  9.   
  10.     public class Human : IncEntityBase  
  11.     {  
  12.         #region Properties  
  13.   
  14.         public virtual DateTime Birthday { getset; }  
  15.   
  16.         public virtual string FirstName { getset; }  
  17.   
  18.         public virtual string Id { getset; }  
  19.   
  20.         public virtual string LastName { getset; }  
  21.   
  22.         public virtual Sex Sex { getset; }  
  23.  
  24.         #endregion  
  25.  
  26.         #region Nested Classes  
  27.   
  28.         public class Map : NHibernateEntityMap<Human>  
  29.         {  
  30.             #region Constructors  
  31.   
  32.             protected Map()  
  33.             {  
  34.                 IdGenerateByGuid(r => r.Id);  
  35.                 MapEscaping(r => r.FirstName);  
  36.                 MapEscaping(r => r.LastName);  
  37.                 MapEscaping(r => r.Birthday);  
  38.                 MapEscaping(r => r.Sex);  
  39.             }  
  40.  
  41.             #endregion  
  42.         }  
  43.  
  44.         #endregion  
  45.     }  
  46.   
  47.     public enum Sex  
  48.     {  
  49.         Male = 1,  
  50.   
  51.         Female = 2  
  52.     }  
  53. }  

Our class contains several fields where we will write data and Nested Class Map.

Note: After creating the Human class, you do not need to perform any operations (creating an XML mapping) due to FluentNhibernate.

We can now add commands and queries, which are responsible for realization of the CRUD operations. The first command will be responsible for adding a new or change an existing record of the Human type. The command is quite simple: we either get an entity on a Repository using the key (ld) or, if no entity exist, we create a new one. Both of these entities get the values specified in the properties of the AddOrEditHumanCommand class. Add Example.Domain, Operations and AddOrEditHumanCommand.cs to the project.

AddOrEditHumanCommand.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using FluentValidation;  
  7.     using Incoding.CQRS;  
  8.     using Incoding.Extensions;  
  9.  
  10.     #endregion  
  11.   
  12.     public class AddOrEditHumanCommand : CommandBase  
  13.     {  
  14.         #region Properties  
  15.   
  16.         public DateTime BirthDay { getset; }  
  17.   
  18.         public string FirstName { getset; }  
  19.   
  20.         public string Id { getset; }  
  21.   
  22.         public string LastName { getset; }  
  23.   
  24.         public Sex Sex { getset; }  
  25.  
  26.         #endregion  
  27.   
  28.         public override void Execute()  
  29.         {  
  30.             var human = Repository.GetById<Human>(Id) ?? new Human();  
  31.   
  32.             human.FirstName = FirstName;  
  33.             human.LastName = LastName;  
  34.             human.Birthday = BirthDay;  
  35.             human.Sex = Sex;  
  36.   
  37.             Repository.SaveOrUpdate(human);  
  38.         }  
  39.     }  
  40. }  

The Read command is the second part of the CRUD. This is a request for reading entities from the DB. Add the file Example.Domain -> Operations -> GetPeopleQuery.cs.

GetPeopleQuery.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System.Collections.Generic;  
  6.     using System.Linq;  
  7.     using Incoding.CQRS;  
  8.  
  9.     #endregion  
  10.   
  11.     public class GetPeopleQuery : QueryBase<List<GetPeopleQuery.Response>>  
  12.     {  
  13.         #region Properties  
  14.   
  15.         public string Keyword { getset; }  
  16.  
  17.         #endregion  
  18.  
  19.         #region Nested Classes  
  20.   
  21.         public class Response  
  22.         {  
  23.             #region Properties  
  24.   
  25.             public string Birthday { getset; }  
  26.   
  27.             public string FirstName { getset; }  
  28.   
  29.             public string Id { getset; }  
  30.   
  31.             public string LastName { getset; }  
  32.   
  33.             public string Sex { getset; }  
  34.  
  35.             #endregion  
  36.         }  
  37.  
  38.         #endregion  
  39.   
  40.         protected override List<Response> ExecuteResult()  
  41.         {  
  42.             return Repository.Query<Human>().Select(human => new Response  
  43.                                                                  {  
  44.                                                                          Id = human.Id,  
  45.                                                                          Birthday = human.Birthday.ToShortDateString(),  
  46.                                                                          FirstName = human.FirstName,  
  47.                                                                          LastName = human.LastName,  
  48.                                                                          Sex = human.Sex.ToString()  
  49.                                                                  }).ToList();  
  50.         }  
  51.     }  
  52. }  

The Delete command is the remaining part of the CRUD. The command deletes records from the DB using the key (ld). Add the file Example.Domain, Operations -> DeleteHumanCommand.cs.

DeleteHumanCommand.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using Incoding.CQRS;  
  6.  
  7.     #endregion  
  8.   
  9.     public class DeleteHumanCommand : CommandBase  
  10.     {  
  11.         #region Properties  
  12.   
  13.         public string HumanId { getset; }  
  14.  
  15.         #endregion  
  16.   
  17.         public override void Execute()  
  18.         {  
  19.             Repository.Delete<Human>(HumanId);  
  20.         }  
  21.     }  
  22. }  

In order to populate the DB with initial data, add the file Example.Domain -> InitPeople.cs that is derived from the ISetUP interface.

ISetup

  1. using System;  
  2.   
  3. namespace Incoding.CQRS  
  4. {  
  5.   public interface ISetUp : IDisposable  
  6.   {  
  7.     int GetOrder();  
  8.   
  9.     void Execute();  
  10.   }  
  11. }  

All the class instances from the ISetUp are registered with IoC in the Bootstrapper.cs (see Introduction) and run (public void Execute() ) in order (public int GetOrder() ).

InitPeople.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using Incoding.Block.IoC;  
  7.     using Incoding.CQRS;  
  8.     using NHibernate.Util;  
  9.  
  10.     #endregion  
  11.   
  12.     public class InitPeople : ISetUp  
  13.     {  
  14.         public void Dispose() { }  
  15.   
  16.         public int GetOrder()  
  17.         {  
  18.             return 0;  
  19.         }  
  20.   
  21.         public void Execute()  
  22.         {  
  23.             //get Dispatcher for execute Query or Command  
  24.             var dispatcher = IoCFactory.Instance.TryResolve<IDispatcher>();  
  25.               
  26.             //don't add new entity if exits  
  27.             if (dispatcher.Query(new GetEntitiesQuery<Human>()).Any())  
  28.                 return;  
  29.   
  30.             //Adding new entity  
  31.             dispatcher.Push(new AddOrEditHumanCommand  
  32.                                 {  
  33.                                         FirstName = "Hellen",  
  34.                                         LastName = "Jonson",  
  35.                                         BirthDay = Convert.ToDateTime("06/05/1985"),  
  36.                                         Sex = Sex.Female  
  37.                                 });  
  38.             dispatcher.Push(new AddOrEditHumanCommand  
  39.                                 {  
  40.                                         FirstName = "John",  
  41.                                         LastName = "Carlson",  
  42.                                         BirthDay = Convert.ToDateTime("06/07/1985"),  
  43.                                         Sex = Sex.Male  
  44.                                 });  
  45.         }  
  46.     }  
  47. }  

The back-end implementation of the CRUD is ready. Now it is the time to add a user code. As in the case of the back end, we begin the implementation with creating/editing a record. Add the file Example.UI, Views, Home, then AddOrEditHuman.cshtml.

AddOrEditHuman.cshtml

  1. @using Example.Domain  
  2. @using Incoding.MetaLanguageContrib  
  3. @using Incoding.MvcContrib  
  4. @model Example.Domain.AddOrEditHumanCommand  
  5. @*Submit form for  AddOrEditHumanCommand*@  
  6. @using (Html.When(JqueryBind.Submit)  
  7.             @*Prevent default behavior and submit form by Ajax*@  
  8.             .PreventDefault()  
  9.             .Submit()  
  10.             .OnSuccess(dsl =>  
  11.                            {  
  12.                                dsl.WithId("PeopleTable").Core().Trigger.Incoding();  
  13.                                dsl.WithId("dialog").JqueryUI().Dialog.Close();  
  14.                            })  
  15.             .OnError(dsl => dsl.Self().Core().Form.Validation.Refresh())  
  16.             .AsHtmlAttributes(new  
  17.                                   {  
  18.                                           action = Url.Dispatcher().Push(new AddOrEditHumanCommand()),  
  19.                                           enctype = "multipart/form-data",  
  20.                                           method = "POST"  
  21.                                   })  
  22.             .ToBeginTag(Html, HtmlTag.Form))  
  23. {  
  24.     <div>  
  25.         @Html.HiddenFor(r => r.Id)  
  26.         @Html.ForGroup(r => r.FirstName).TextBox(control => control.Label.Name = "First name")  
  27.         <br/>  
  28.         @Html.ForGroup(r => r.LastName).TextBox(control => control.Label.Name = "Last name")  
  29.         <br/>  
  30.         @Html.ForGroup(r => r.BirthDay).TextBox(control => control.Label.Name = "Birthday")  
  31.         <br/>  
  32.         @Html.ForGroup(r => r.Sex).DropDown(control => control.Input.Data = typeof(Sex).ToSelectList())  
  33.     </div>  
  34.   
  35.     <div>  
  36.         <input type="submit" value="Save"/>  
  37.         @*Закрытие диалога*@  
  38.         @(Html.When(JqueryBind.Click)  
  39.               .PreventDefault()  
  40.               .StopPropagation()  
  41.               .Direct()  
  42.               .OnSuccess(dsl => { dsl.WithId("dialog").JqueryUI().Dialog.Close(); })  
  43.               .AsHtmlAttributes()  
  44.               .ToButton("Cancel"))  
  45.     </div>  
  46. }  

The IML-code creates the standard HTML form and works with AddOrEditHumanCommand, sending the appropriate Ajax query to the server. Then comes the template for data loading through the GetPeopleQuery. There is a description of the table that will be responsible not only for data output, but also for record deletion and editing: add the file Example.UI, Views, Home, then HumanTmpl.cshtml.

HumanTmpl.cshtml

  1. @using Example.Domain  
  2. @using Incoding.MetaLanguageContrib  
  3. @using Incoding.MvcContrib  
  4. @{  
  5.     using (var template = Html.Incoding().Template<GetPeopleQuery.Response>())  
  6.     {  
  7.         <table class="table">  
  8.             <thead>  
  9.             <tr>  
  10.                 <th>  
  11.                     First name  
  12.                 </th>  
  13.                 <th>  
  14.                     Last name  
  15.                 </th>  
  16.                 <th>  
  17.                     Birthday  
  18.                 </th>  
  19.                 <th>  
  20.                     Sex  
  21.                 </th>  
  22.                 <th></th>  
  23.             </tr>  
  24.             </thead>  
  25.             <tbody>  
  26.             @using (var each = template.ForEach())  
  27.             {  
  28.                 <tr>  
  29.                     <td>  
  30.                         @each.For(r => r.FirstName)  
  31.                     </td>  
  32.                     <td>  
  33.                         @each.For(r => r.LastName)  
  34.                     </td>  
  35.                     <td>  
  36.                         @each.For(r => r.Birthday)  
  37.                     </td>  
  38.                     <td>  
  39.                         @each.For(r => r.Sex)  
  40.                     </td>  
  41.                     <td>  
  42.                         @*Open edit dialog form*@  
  43.                         @(Html.When(JqueryBind.Click)  
  44.                               .AjaxGet(Url.Dispatcher().Model<AddOrEditHumanCommand>(new  
  45.                                                                                          {  
  46.                                                                                                  Id = each.For(r => r.Id),  
  47.                                                                                                  FirstName = each.For(r => r.FirstName),  
  48.                                                                                                  LastName = each.For(r => r.LastName),  
  49.                                                                                                  BirthDay = each.For(r => r.Birthday),  
  50.                                                                                                  Sex = each.For(r => r.Sex)  
  51.                                                                                          }).AsView("~/Views/Home/AddOrEditHuman.cshtml"))  
  52.                               .OnSuccess(dsl => dsl.WithId("dialog").Behaviors(inDsl =>  
  53.                                                                                    {  
  54.                                                                                        inDsl.Core().Insert.Html();  
  55.                                                                                        inDsl.JqueryUI().Dialog.Open(option =>  
  56.                                                                                                                         {  
  57.                                                                                                                             option.Resizable = false;  
  58.                                                                                                                             option.Title = "Edit human";  
  59.                                                                                                                         });  
  60.                                                                                    }))  
  61.                               .AsHtmlAttributes()  
  62.                               .ToButton("Edit"))  
  63.                         @*Button delete*@  
  64.                         @(Html.When(JqueryBind.Click)  
  65.                               .AjaxPost(Url.Dispatcher().Push(new DeleteHumanCommand() { HumanId = each.For(r => r.Id) }))  
  66.                               .OnSuccess(dsl => dsl.WithId("PeopleTable").Core().Trigger.Incoding())  
  67.                               .AsHtmlAttributes()  
  68.                               .ToButton("Delete"))  
  69.                     </td>  
  70.                 </tr>  
  71.             }  
  72.             </tbody>  
  73.         </table>  
  74.     }  
  75. }  

Note: The task of opening a dialog box is quite common, so the code that is responsible for this task can be exported to the extension. Thus, it remains to change the start page so that during loading, the AJAX query is transmitted to the server for obtaining data from the GetPeopleQuery and mapping of data using HumanTmpl: change the file Example.UI, View, Home, then Index.cshtml so that it looks like the following code snippet:

Index.cshtml

  1. @using Example.Domain  
  2. @using Incoding.MetaLanguageContrib  
  3. @using Incoding.MvcContrib  
  4. @{  
  5.     Layout = "~/Views/Shared/_Layout.cshtml";  
  6. }  
  7. <div id="dialog"></div>  
In real-world applications, validation of input form data is one of the most frequent task. Therefore, we add data validation on the adding/editing form of the Human entity. Firstly, we need to add a server code. Add the following code in AddOrEditHumanCommand as a nested class.
  1. #region Nested Classes  
  2.   
  3. public class Validator : AbstractValidator  
  4. {  
  5.     #region Constructors  
  6.   
  7.     public Validator()  
  8.     {  
  9.         RuleFor(r => r.FirstName).NotEmpty();  
  10.         RuleFor(r => r.LastName).NotEmpty();  
  11.     }  
  12.  
  13.     #endregion  
  14. }  
  15.  
  16. #endregion  

On the AddOrEditHuman.cshtml form, we used constructs like this:Hide Copy Code.

@Html.ForGroup()

It is therefore not necessary to add

@Html.ValidationMessageFor()

for the fields - ForGroup() will do it.

So we have written the application code that implements the CRUD functionality for one DB entity.

Part 4: Specifications - data cleaning

Another task that often occurs in real projects is to clean requested data. Incoding Framework uses WhereSpecifications for convenient code writing and complying an encapsulation principle for cleaning data from Query. In the written code add a possibility to clean data from GetPeopleQuery by FirstName and LastName. Firstly, add two specification files Example.Domain, Specifications, then HumanByFirstNameWhereSpec.cs and Example.UI,  Specifications, then HumanByLastNameWhereSpec.cs

HumanByFirstNameWhereSpec.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using System.Linq.Expressions;  
  7.     using Incoding;  
  8.  
  9.     #endregion  
  10.   
  11.     public class HumanByFirstNameWhereSpec : Specification  
  12.     {  
  13.         #region Fields  
  14.   
  15.         readonly string firstName;  
  16.  
  17.         #endregion  
  18.  
  19.         #region Constructors  
  20.   
  21.         public HumanByFirstNameWhereSpec(string firstName)  
  22.         {  
  23.             this.firstName = firstName;  
  24.         }  
  25.  
  26.         #endregion  
  27.   
  28.         public override Expression<Func<Human, bool>> IsSatisfiedBy()  
  29.         {  
  30.             if (string.IsNullOrEmpty(this.firstName))  
  31.                 return null;  
  32.   
  33.             return human => human.FirstName.ToLower().Contains(this.firstName.ToLower());  
  34.         }  
  35.     }  
  36. }  
HumanByLastNameWhereSpec.cs
  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using System.Linq.Expressions;  
  7.     using Incoding;  
  8.  
  9.     #endregion  
  10.   
  11.     public class HumanByLastNameWhereSpec : Specification  
  12.     {  
  13.         #region Fields  
  14.   
  15.         readonly string lastName;  
  16.  
  17.         #endregion  
  18.  
  19.         #region Constructors  
  20.   
  21.         public HumanByLastNameWhereSpec(string lastName)  
  22.         {  
  23.             this.lastName = lastName.ToLower();  
  24.         }  
  25.  
  26.         #endregion  
  27.   
  28.         public override Expression<Func<Human, bool>> IsSatisfiedBy()  
  29.         {  
  30.             if (string.IsNullOrEmpty(this.lastName))  
  31.                 return null;  
  32.   
  33.             return human => human.LastName.ToLower().Contains(this.lastName);  
  34.         }  
  35.     }  
  36. }  

Now use the written specifications in GetPeopleQuery. .Or()/.And() relations allow to merge atomic specifications that helps to use the created specifications many times and fine-tune necessary data filters (in the example we use .Or() relation)

GetPeopleQuery.cs

  1. namespace Example.Domain  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System.Collections.Generic;  
  6.     using System.Linq;  
  7.     using Incoding.CQRS;  
  8.     using Incoding.Extensions;  
  9.  
  10.     #endregion  
  11.   
  12.     public class GetPeopleQuery : QueryBase<List<GetPeopleQuery.Response>>  
  13.     {  
  14.         #region Properties  
  15.   
  16.         public string Keyword { getset; }  
  17.  
  18.         #endregion  
  19.  
  20.         #region Nested Classes  
  21.   
  22.         public class Response  
  23.         {  
  24.             #region Properties  
  25.   
  26.             public string Birthday { getset; }  
  27.   
  28.             public string FirstName { getset; }  
  29.   
  30.             public string Id { getset; }  
  31.   
  32.             public string LastName { getset; }  
  33.   
  34.             public string Sex { getset; }  
  35.  
  36.             #endregion  
  37.         }  
  38.  
  39.         #endregion  
  40.   
  41.         protected override List<Response> ExecuteResult()  
  42.         {  
  43.             return Repository.Query(whereSpecification: new HumanByFirstNameWhereSpec(Keyword)  
  44.                                             .Or(new HumanByLastNameWhereSpec(Keyword)))  
  45.                              .Select(human => new Response  
  46.                                                   {  
  47.                                                           Id = human.Id,  
  48.                                                           Birthday = human.Birthday.ToShortDateString(),  
  49.                                                           FirstName = human.FirstName,  
  50.                                                           LastName = human.LastName,  
  51.                                                           Sex = human.Sex.ToString()  
  52.                                                   }).ToList();  
  53.         }  
  54.     }  
  55. }  

Finally, it only remains to modify Index.cshtml in order to add a search box that uses a Keyword field for data cleaning while a request is being processed.

Index.cshtml

  1. @using Example.Domain  
  2. @using Incoding.MetaLanguageContrib  
  3. @using Incoding.MvcContrib  
  4. @{  
  5.     Layout = "~/Views/Shared/_Layout.cshtml";  
  6. }  
  7. <div id="dialog"></div>  

Part 5: Unit-test

Let’s cover the written code with tests. The first one is responsible for testing of Human entity mapping. Add the file When_save_Human.cs to the folder Persistences of the UnitTests project.

When_save_Human.cs

  1. namespace Example.UnitTests.Persistences  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using Example.Domain;  
  6.     using Incoding.MSpecContrib;  
  7.     using Machine.Specifications;  
  8.  
  9.     #endregion  
  10.   
  11.     [Subject(typeof(Human))]  
  12.     public class When_save_Human : SpecWithPersistenceSpecification  
  13.     {  
  14.         #region Fields  
  15.   
  16.         It should_be_verify = () => persistenceSpecification.VerifyMappingAndSchema();  
  17.  
  18.         #endregion  
  19.     }  
  20. }  

The test works with a test database (Example_test): an instance of the Human class with automatically populated fields is created, then stored in the DB, retrieved from and compared to the created instance. Then add the tests for WhereSpecifications in a folder named Specifications.

When_human_by_first_name.cs

  1. namespace Example.UnitTests.Specifications  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using System.Collections.Generic;  
  7.     using System.Linq;  
  8.     using Example.Domain;  
  9.     using Incoding.MSpecContrib;  
  10.     using Machine.Specifications;  
  11.  
  12.     #endregion  
  13.   
  14.     [Subject(typeof(HumanByFirstNameWhereSpec))]  
  15.     public class When_human_by_first_name  
  16.     {  
  17.         #region Fields  
  18.   
  19.         Establish establish = () =>  
  20.                                   {  
  21.                                       Func<string, Human> createEntity = (firstName) =>  
  22.                                                                       Pleasure.MockStrictAsObject(mock =>  
  23.                                                                              mock.SetupGet(r => r.FirstName).Returns(firstName));  
  24.   
  25.                                       fakeCollection = Pleasure.ToQueryable(createEntity(Pleasure.Generator.TheSameString()),  
  26.                                                                             createEntity(Pleasure.Generator.String()));  
  27.                                   };  
  28.   
  29.         Because of = () =>  
  30.                          {  
  31.                              filterCollection = fakeCollection  
  32.                                      .Where(new HumanByFirstNameWhereSpec(Pleasure.Generator.TheSameString()).IsSatisfiedBy())  
  33.                                      .ToList();  
  34.                          };  
  35.   
  36.         It should_be_filter = () =>  
  37.                                   {  
  38.                                       filterCollection.Count.ShouldEqual(1);  
  39.                                       filterCollection[0].FirstName.ShouldBeTheSameString();  
  40.                                   };  
  41.  
  42.         #endregion  
  43.  
  44.         #region Establish value  
  45.   
  46.         static IQueryable fakeCollection;  
  47.   
  48.         static List filterCollection;  
  49.  
  50.         #endregion  
  51.     }  
  52. }  
When_human_by_last_name.cs
  1. namespace Example.UnitTests.Specifications  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System;  
  6.     using System.Collections.Generic;  
  7.     using System.Linq;  
  8.     using Example.Domain;  
  9.     using Incoding.MSpecContrib;  
  10.     using Machine.Specifications;  
  11.  
  12.     #endregion  
  13.   
  14.     [Subject(typeof(HumanByLastNameWhereSpec))]  
  15.     public class When_human_by_last_name  
  16.     {  
  17.         #region Fields  
  18.   
  19.         Establish establish = () =>  
  20.                                   {  
  21.                                       Func<string, Human> createEntity = (lastName) =>  
  22.                                                                          Pleasure.MockStrictAsObject(mock =>  
  23.                                                                                                             mock.SetupGet(r => r.LastName)  
  24.                                                                                                                 .Returns(lastName));  
  25.   
  26.                                       fakeCollection = Pleasure.ToQueryable(createEntity(Pleasure.Generator.TheSameString()),  
  27.                                                                             createEntity(Pleasure.Generator.String()));  
  28.                                   };  
  29.   
  30.         Because of = () =>  
  31.                          {  
  32.                              filterCollection = fakeCollection  
  33.                                      .Where(new HumanByLastNameWhereSpec(Pleasure.Generator.TheSameString()).IsSatisfiedBy())  
  34.                                      .ToList();  
  35.                          };  
  36.   
  37.         It should_be_filter = () =>  
  38.                                   {  
  39.                                       filterCollection.Count.ShouldEqual(1);  
  40.                                       filterCollection[0].LastName.ShouldBeTheSameString();  
  41.                                   };  
  42.  
  43.         #endregion  
  44.  
  45.         #region Establish value  
  46.   
  47.         static IQueryable fakeCollection;  
  48.   
  49.         static List filterCollection;  
  50.  
  51.         #endregion  
  52.     }  
  53. }  

Now we have to add tests for the command and the query (Operations folder). For the command, you need to add two tests: the first one verifies the creation of a new entity; the second one verifies the editing of an existing entity.

When_get_people_query.cs

  1. namespace Example.UnitTests.Operations  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using System.Collections.Generic;  
  6.     using Example.Domain;  
  7.     using Incoding.Extensions;  
  8.     using Incoding.MSpecContrib;  
  9.     using Machine.Specifications;  
  10.  
  11.     #endregion  
  12.   
  13.     [Subject(typeof(GetPeopleQuery))]  
  14.     public class When_get_people  
  15.     {  
  16.         #region Fields  
  17.   
  18.         Establish establish = () =>  
  19.                                   {  
  20.                                       var query = Pleasure.Generator.Invent<GetPeopleQuery>();  
  21.                                       //Create entity for test with auto-generate  
  22.                                       human = Pleasure.Generator.Invent<Human>();  
  23.   
  24.                                       expected = new List<GetPeopleQuery.Response>();  
  25.   
  26.                                       mockQuery = MockQuery<GetPeopleQuery, List<GetPeopleQuery.Response>>  
  27.                                               .When(query)  
  28.                                               //"Stub" on query to repository  
  29.                                               .StubQuery(whereSpecification: new HumanByFirstNameWhereSpec(query.Keyword)  
  30.                                                                  .Or(new HumanByLastNameWhereSpec(query.Keyword)),  
  31.                                                          entities: human);  
  32.                                   };  
  33.   
  34.         Because of = () => mockQuery.Original.Execute();  
  35.           
  36.         // Compare result   
  37.         It should_be_result = () => mockQuery.ShouldBeIsResult(list => list.ShouldEqualWeakEach(new List<Human>() { human },  
  38.                                                                                                 (dsl, i) => dsl.ForwardToValue(r => r.Birthday, human.Birthday.ToShortDateString())  
  39.                                                                                                                .ForwardToValue(r => r.Sex, human.Sex.ToString())  
  40.                                                                                ));  
  41.  
  42.         #endregion  
  43.  
  44.         #region Establish value  
  45.   
  46.         static MockMessage<GetPeopleQuery, List<GetPeopleQuery.Response>> mockQuery;  
  47.   
  48.         static List<GetPeopleQuery.Response> expected;  
  49.   
  50.         static Human human;  
  51.  
  52.         #endregion  
  53.     }  
  54. }  

When_add_human.cs

  1. namespace Example.UnitTests.Operations  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using Example.Domain;  
  6.     using Incoding.MSpecContrib;  
  7.     using Machine.Specifications;  
  8.  
  9.     #endregion  
  10.   
  11.     [Subject(typeof(AddOrEditHumanCommand))]  
  12.     public class When_add_human  
  13.     {  
  14.         #region Fields  
  15.   
  16.         Establish establish = () =>  
  17.                                   {  
  18.                                       var command = Pleasure.Generator.Invent<AddOrEditHumanCommand>();  
  19.   
  20.                                       mockCommand = MockCommand<AddOrEditHumanCommand>  
  21.                                               .When(command)  
  22.                                               //"Stub" on repository  
  23.                                               .StubGetById<Human>(command.Id, null);  
  24.                                   };  
  25.   
  26.         Because of = () => mockCommand.Original.Execute();  
  27.   
  28.         It should_be_saved = () => mockCommand.ShouldBeSaveOrUpdate<Human>(human => human.ShouldEqualWeak(mockCommand.Original));  
  29.  
  30.         #endregion  
  31.  
  32.         #region Establish value  
  33.   
  34.         static MockMessage<AddOrEditHumanCommand, object> mockCommand;  
  35.  
  36.         #endregion  
  37.     }  
  38. }  

When_edit_human.cs

  1. namespace Example.UnitTests.Operations  
  2. {  
  3.     #region << Using >>  
  4.   
  5.     using Example.Domain;  
  6.     using Incoding.MSpecContrib;  
  7.     using Machine.Specifications;  
  8.  
  9.     #endregion  
  10.   
  11.     [Subject(typeof(AddOrEditHumanCommand))]  
  12.     public class When_edit_human  
  13.     {  
  14.         #region Fields  
  15.   
  16.         Establish establish = () =>  
  17.                                   {  
  18.                                       var command = Pleasure.Generator.Invent<AddOrEditHumanCommand>();  
  19.   
  20.                                       human = Pleasure.Generator.Invent<Human>();  
  21.   
  22.                                       mockCommand = MockCommand<AddOrEditHumanCommand>  
  23.                                               .When(command)  
  24.                                               //"Stub" on repository  
  25.                                               .StubGetById(command.Id, human);  
  26.                                   };  
  27.   
  28.         Because of = () => mockCommand.Original.Execute();  
  29.   
  30.         It should_be_saved = () => mockCommand.ShouldBeSaveOrUpdate<Human>(human => human.ShouldEqualWeak(mockCommand.Original));  
  31.  
  32.         #endregion  
  33.  
  34.         #region Establish value  
  35.   
  36.         static MockMessage<AddOrEditHumanCommand, object> mockCommand;  
  37.   
  38.         static Human human;  
  39.  
  40.         #endregion  
  41.     }  
  42. }  

Study materials

  1. CQRS and CQRS (advanced course) , Repository - back end architecture.
  2. IML template - Templates for data insertion.
  3. Unit Test and Unit test scenario.


Similar Articles