Click here to Skip to main content
15,881,687 members
Articles / Web Development / HTML

Incoding Framework - Get started

Rate me:
Please Sign up or sign in to vote.
4.81/5 (5 votes)
5 Sep 2015CPOL9 min read 21.4K   40   4   16
Disclamer: this article is a step-by-step guide to help you to familiarize with the core functionality of Incoding Framework. Following the guide will result in an application that implements the work with the DB (CRUD + data filters) and fully covered with unit tests.

IncFramework-logo

UPD:  Source code available on Github and as attached file. Please don't forget change connection string to your data base. 
 
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 3 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 in 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, StructureMapis installed as loC framework, but there is a provider for Ninject, and it is also possible to write your own implementations.

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

Further on, commands (Command) and queries (Query) are added to 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.

C#
public static class IncodingStart
{
    public static void PreStart()
    {
        Bootstrapper.Start();
        new DispatcherController(); // init routes
    }
}
C#
public class DispatcherController : DispatcherControllerBase
{
    #region Constructors

    public DispatcherController()
            : base(typeof(Bootstrapper).Assembly) { }

    #endregion
}

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

UnitTests

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

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

Part 1. Installation.

So, we proceed to the task of the disclamer  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 3 projects. We begin with the project that is responsible for business logic of the application - Domain.

Create class library Domain.

Domain

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

UI1 UI2

Finally, we add class library UnitTests that is responsible for unit testing.

UnitTests

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 following solution: Solution After we create the solution structure, we need to install Incoding Framework package from Nuget. The installation carried out by Nuget. There 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

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

StructureMap_ref 2 packages must be installed to UI:

  1. Incoding Meta Language
  2. Incoding Meta Language Contrib

Incoding_Meta_Languge

MetaLanguageContrib_install

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 -> Views -> Shared -> _Layout.cshtml so that it looks as follows:

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

Then add the link to Bootstrapper.cs to the files Example.UI -> App_Start -> IncodingStart.cs and Example.UI ->  IncodingStart_bootstrapper

Controllers -> DispatcherController.cs.DispatcherController_bootstrapper

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

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

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

Incoding_tests_helpers MSpecAssemblyContext_bootstrapper The last phase of the preparation the projects to work is to create folders structure for the projects. Add following folders to the Example.Domain project:

  1. Operations – command and query of the project
  2. Persistences – entities for DB mapping
  3. Specifications – where and order specifications for data cleaning when request is made

Example.Domain_folders

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

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

example_db

example_test_db

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

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

In the file Example.Domain -> Infrastructure -> Bootstrapper.cs, register the appropriate connection string using a key called Example:

XML
//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 connection string to the BD using the key called Example_test:

C#
//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: 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 this 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

C#
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 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 -> AddOrEditHumanCommand.cs 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 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

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

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

C#
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 populate the DB with initial data, add the file Example.Domain -> InitPeople.cs that is derived from the ISetUP interface.

ISetup

C#
using System;

namespace Incoding.CQRS
{
  public interface ISetUp : IDisposable
  {
    int GetOrder();

    void Execute();
  }
}

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

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

The back-end implementation of the CRUD is ready. Now it is 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 -> AddOrEditHuman.cshtml. 

AddOrEditHuman.cshtml

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

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 -> HumanTmpl.cshtml.

HumanTmpl.cshtml

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

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 its loading AJAX query is transmitted to the server for obtaining data from the GetPeopleQuery and mapping of data using HumanTmpl: change the file Example.UI -> Views -> Home -> Index.cshtml so that it looks as follows.

Index.cshtml

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

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. First, we need to add a server code. Add the following code in AddOrEditHumanCommand as a 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 AddOrEditHuman.cshtml form, we used constructs like this:

@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. First, add two specification files Example.Domain -> Specifications -> HumanByFirstNameWhereSpec.cs and Example.UI -> Specifications -> HumanByLastNameWhereSpec.cs

HumanByFirstNameWhereSpec.cs

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

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

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 only remains to modify Index.cshtml in order to add a search box, which uses a Keyword field for data cleaning while a request is being processed.

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

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

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

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

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

Study materials

  1. CQRS  and CQRS (advanced course) , Repository - back end architecture
  2. MVD -a description of a Model View Dispatcher pattern
  3. IML (TODO), IML vs Angular , Iml vs Jquery , Iml vs ASP.NET Ajax - incoding meta language
  4. IML (selector)-  a description of the selectors’ usage in IML
  5. IML In Ajax - a description of the IML Operation in relation to Ajax
  6. IML template - Templates for data insertion
  7. Extensions- help with writing extensions to  comply the Don'tRepeatYourself principle
  8. Unit Test and Unit test scenario

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Incoding Software
Russian Federation Russian Federation
I am a Senior developer at Incoding Software in Russian Federation. I created the Incoding Framework for rapid development

Comments and Discussions

 
QuestionHave you consider to post this as a tip? Pin
Nelek15-Sep-15 3:37
protectorNelek15-Sep-15 3:37 
AnswerRe: Have you consider to post this as a tip? Pin
Wladis15-Sep-15 3:49
Wladis15-Sep-15 3:49 
GeneralRe: Have you consider to post this as a tip? Pin
Nelek15-Sep-15 4:31
protectorNelek15-Sep-15 4:31 
GeneralRe: Have you consider to post this as a tip? Pin
Wladis15-Sep-15 4:53
Wladis15-Sep-15 4:53 
GeneralRe: Have you consider to post this as a tip? Pin
Nelek15-Sep-15 18:35
protectorNelek15-Sep-15 18:35 
QuestionHome page coming blank Pin
paksys8-Sep-15 15:25
paksys8-Sep-15 15:25 
AnswerRe: Home page coming blank Pin
Wladis8-Sep-15 20:10
Wladis8-Sep-15 20:10 
GeneralRe: Home page coming blank Pin
paksys9-Sep-15 15:54
paksys9-Sep-15 15:54 
GeneralRe: Home page coming blank Pin
Wladis9-Sep-15 20:16
Wladis9-Sep-15 20:16 
GeneralRe: Home page coming blank Pin
Wladis15-Sep-15 4:15
Wladis15-Sep-15 4:15 
QuestionSome russian slipped in ... Pin
Roman Ivantsov7-Sep-15 8:14
professionalRoman Ivantsov7-Sep-15 8:14 
В целом Incoding Framework is used as ...
AnswerRe: Some russian slipped in ... Pin
Wladis7-Sep-15 8:23
Wladis7-Sep-15 8:23 
Question+ 5 / Question: Performance? Pin
RAND 4558665-Sep-15 22:24
RAND 4558665-Sep-15 22:24 
AnswerRe: + 5 / Question: Performance? Pin
Wladis6-Sep-15 1:45
Wladis6-Sep-15 1:45 
AnswerThank! Pin
RAND 45586616-Sep-15 7:07
RAND 45586616-Sep-15 7:07 
GeneralRe: Thank! Pin
Wladis16-Sep-15 7:09
Wladis16-Sep-15 7:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.