Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Expanding The Catharsis Demo Project

0.00/5 (No votes)
22 Jun 2009 1  
A step by step guide to adding entities to a Catharsis application

Adding Entities To A Catharsis Application

This document is a step by step guide to adding new entities to a Catharsis web application using the automated guidance which creates the skeleton classes and multi-teir architecture automatically.

In this document we will add a new entity to the demo solution for the Catharsis Framework which is called Firm.SmartControls.

A good way to lean more about Catharsis is to install the demo solution and follow the step by step guide below.

Prerequisites

The author of Catharsis, Radim Kohler, has published many documents on the ASP.NET MVS Catharsis Framework and these can be found here:
http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=5517102

The Catharsis source code and the installer for the demo project (Firm.SmartControls) can be found here:
http://www.codeplex.com/Catharsis

Before you read this document please read my previous one which takes a brief look at the demo project and therefore acts as a good primer for this document. You can find the document, entitled The Catharsis Demo Project, here:
http://www.codeproject.com/KB/aspnet/TheCatharsisDemoProject.aspx

Using the Guidance

The database create script for the Firm.SmartControls solution actually contains two tables which are not yet mapped so we will use one of these as an example of how to add a new entity.

Insurance Client database table

'Client' will be our new entity. (We will add it to the Insurance namespace).

Before we can add the new entity using Guidance we need to enable the Guidance package.
Click on Tools -> Guidance Package Manager

Guidance Package Manager

Click Enable/Disable Packages on the dialog that appears, select ProjectBase.Guidance and click okay.

Enable and Disable Packages dialog

Close the next two dialogs that appear as we do not need them now.

Note that if you want to add a complete web infrastructure the best place to add it is via the Entity (or Web project). It is also possible to add it in the DataLayer but this would exclude the GUI elements which we will require in this instance.

The folder into which the new entity is added will become part of the namespace for that new entity. If you want the entity to be in a new namespace you should create a folder in the entity project and add it there, alternatively you can add it to an existing folder as we will do in this case because we want our new entity to be in the Insurance namespace.

Right click on the folder and select "(Re) Create COMPLETE WEB infrastructure" from the menu. ‘(Re)’ signifies that if the entity has already exists in the folder the files will be overwritten with new empty skeleton classes, this offers a way to undo code or correct mistakes.
"COMPLETE WEB" means, that skeleton class-files will be added to every project (even unit tests).
If you select the "(Re) Create Project - (Entity, Business, Common, Tests)" - no files in the Models, Controllers and Web projects will be added (or changed). This is for cases where no GUI elements are required.

The namespace of the new entity should be Firm.SmartControls.Entity.Insurance.Client so we click on the Insurance folder as shown to generate the web infrastructure via Guidance.

The Guidance menu options

Here is the main dialog which we need to fill. Giving as much information here as possible will reduce the amount of work that we need to do later.

Create Web Infrastructure dialog

Type the name of the new entity in the dialog. You can now add up to three additional properties.

In SQL Server 2005 we saw the columns of the InsuranceClient table so we can add the first three, Code, Name and BankCode. Guidance will automatically generate checks to ensure that Code is unique (this can be deleted if it is not required). Adding properties here reduces the amount of work that we will have to do later, however we can only add value types, for example a string property ‘name’, we cannot add Entity types, for example a foreign key which references another table such as Country.

The Namespace is provided because it is determined by where you added the entity in the solution.

The entity type in this case should be 'Persistent'. That's will create the skeleton for business object which has no ancestors for the business type (it is derived directly from the Persistent object).
Other types allow reusing previously implemented functionality.

The second and third options are for CodeLists, you can choose ‘simple’ or ‘separate entity’. First let we need to be clear on what a CodeList is. CodeList entities are often used to populate comboboxes, for allowing the user to selct one option from a collection of predefined options. All of the countries in the EU could be represented in this way, the genders Male and Female are another good example. Another general propertu of CodeList entities is that the data is static, it will not be necessary to add or delete objects of this type. Gender, for example will never need more than ‘Male’ and ‘Female’.

The base classes give CodeList entities a code and a name, so for Gender the name could be Male and the code could be M. These are simple entities because no additional information is required. Therefore using the option for simple CodeList is suitable for something like Gender or Country. If you need a simple entity like Gender you can use the ICodeList option. In that case, you do not have to implement anything, your new ICodeList property will be working immediately without any additional coding.

The framework also gives the option to create a CodeList but allows for the entity to be extended with additional information. Currency, for example could have an object with the Name ‘Dollar’ and the Code ‘USD’ but we might also want to add a column for subunit and give it a value of ‘cents’ or ‘c’. If we need to extend the basic functionality of a CodeList the ‘separate entity’ can be used. In this case a column in the database table should hold a value for the subunit.

The Tracked entity type is the same as a Persistent type but additional code is provided which allows an ‘Audit Trail’ to be maintained for the entity. If you need to track when an entity is changed, who changed it and what state it is in this is the best option.

Select entity type dialog

Click Finish and after some time all of the files will be automatically generated and a pop-up will appear to tell you what you should do next:

Catharsis ToDo pop-up

So we follow these instructions and open the Str.Controller.cs file and add the highlighted line:

AddToCatProj008.png

Now we open the menu.config file

Menu.config file location

The highlighted code should be added:

Menu.config code to be added

It is added at the same level as Agent so it will appear as a sibling of this node in the Navigation tree.

New entity in the menu navigation tree

Attempting to use this new entry in the navigation menu will cause an error of course because the database table has not yet been mapped via NHibernate:

Error due to incomplete code

Mapping the database table to the entity via NHibernate

The table that we are mapping looks like this:

InsuranceClient database table

Open the NHibernate file which was automatically generated for this entity.

Firm.SmartControls.Data.Insurance.Clicne.hbm.xml

The data which you supplied during the creation of the entity is already added:



  
      
        
      
    
    
    
  

The following sections need to be changed:

The table name is InsuranceClient, not Cient.
The ID column is InsuranceClientId, not ClientId.
CountryId and GenderId are CodeLists and will require many-to-one mappings.



  
      
        
      
    
    
    

    
    
    
  

We can see a reference to Firm.SmartControls.Entity.Insurance in the file above so this will need to be changed to reflect the changes we made in the mapping file.

The DAO (Data Access Object) will also need to be changed but before we do that we will add the properties to the Entity file which were not automatically generated by the Guidance.

Open the Client entity file

Client entity file

Three properties exist, Code, Name and BankCode. We will now add Gender and Country. These are CodeList objects so we need to add a using directive for Firm.SmartControls.Entity.CodeLists in order for the Gender and Country datatypes to be recognised. The code we should add is included below.

using System;                            // ===================================
using System.Collections.Generic;        // Guidance generated code © Catharsis
using System.Linq;                       // ===================================

using ProjectBase.Core;
using ProjectBase.Core.PersistentObjects;
using ProjectBase.Core.Collections;
using Firm.SmartControls.Entity.CodeLists;

namespace Firm.SmartControls.Entity.Insurance
{
    /// 
    /// Entity Client. 
    /// 
    [Serializable]
    public class Client : Persistent
    {
        public virtual string Code { get; set; }
        public virtual string Name { get; set; }
        public virtual string BankCode { get; set; }

        /// 
        /// codelist
        /// 
        public virtual Gender Gender { get; set; }
        /// 
        /// codelist
        /// 
        public virtual Country Country { get; set; }

Now we add these additional fields to the DAO (Firm.SmartControls.Data.Insurance.ClientDao)

The newly added Gender and Country should be available in intellisense when we add the two new entries, the is obviously because they are now properties of the Client entity.

Intellisense working in the Client entity file.

Running the application with the new entity

Now we have made the necessary changes to the NHibernate file, the Entity and the Dao so the 'Client' menu item will work.

The new menu item

Of course there are no Clients in the database yet so we will need to add these. The next step is to extend the functionality behind the ‘New’ button to allow us to add new Clients.

Adding new Clients

If we click the 'New' button now we will see that the properties which we specified during the Guidance setup (Code, Name and BankCode) are automatically added.

Automatically created entity properties

Now we will add the Gender and Country properties.

Open the ClientDetailsWC.ascx file (The abbreviation WC is for ‘Web Control’)

ClientDetailsWC.ascx file location

This file shows the HTML Markup used to create the page shown above.

We will reduce the size of the two columns (Identification and Description) and add a third column for CodeLists and will add CodeLists for Gender and Country.

Each Row in the HTML contains a number of fieldsets. There is currently one fieldset for Identification and one for Description. We will reduce the percentage width of these two to 32% so we will have enough room in the row for three fieldsets.

w32p represents a CSS class for width. We can examine these CSS classes in the following file:

CSS file location

The css style .w32p { width: 32%; } will be used in our case.

Now we can add a third fieldset for the two CodeLists, the code is shown here:

<%= GetLocalized(Str.Business.CodeLists.CodeList)%>
<%= GetLocalized(Str.Controllers.Country)%>
<% Country.SetEntityAsDataSource(Model.Item.Country); %> <smart:AsyncComboBoxWC ID="Country" runat="server" TargetController="Country" />
class="inputWC inputWC60 w100p"> class="label"><%= GetLocalized(Str.Controllers.Gender)%>
<% Gender.SetEntityAsDataSource(Model.Item.Gender); %> <smart:AsyncComboBoxWC ID="Gender" runat="server" TargetController="Gender" />

This code uses the Model to access the item (the entity) and it’s properties. Now we can see that two new dropdown lists have been added and populated with the data that we require.

New Client screen with working entity-types

Attempting to actually add a new Client will fail

Add Client Error Message

This is because when we click the add button the Controller will try to add the entity but it cannot do so because it cannot yet handle the entity-type properties.

We need to look at the Controller for the new entity which has been automatically generated at the following location:

Client Controller file location

Here are many regions which are available for us to add code to, most of these are empty in a newly created Controller file.

Controller file regions

It may be useful to know that holding down the CTRL key and typing m o will expand all the regions, likewise CTRL ml will collapse all the regions.

Members: This is used to hold local variables which are used in this class.
OnList: This region contains overridden methods which we can use to extend the functionality for creating a list of entities to display in the ‘List’ view in the application and also to export that list to an Excel spreadsheet.
OnAfter: This region contains some overridden methods which are used to perform tasks after certain events have taken place. OnAfterBindModel and OnAfterBindSearch are implemented in the default Controller. These two are used to take care of entity-type properties so we will use these to add Country and Gender.
OnBefore: Like the region above we can override several methods from the base classes in this section. It is possible to see a list of the available methods by typing ‘override OnBefore’ and then the intellisense will show us the available methods.

Available methods to override

Actions: This region could be used to override methods relating to Actions but if we do not need this we can simply delete the region. It is important to realise that a lot of the automatically generated code might not be needed for your particular needs and therefore can be deleted. It is easier to delete unneeded code than to write missing code.
ClearSearch: This is used to remove parameters from a search object after it has been used.
Properties: This region has a method which returns the name of the current Controller, which will of course be ClientController in our case. It is also used to load entities which we may need if they are entity types in our entity. This will of course be the case in our example because we will have two entity types, Country and Gender. These objects use the ‘lazy load’ approach, this means that they are only created when they are needed, this improves efficiency in the application.

So now we will make the required changes to allow us to save a new Client.

We need to add two methods to the OnAfter method to handle the entity types:

/// 
/// Binds non value type properties for an Item
/// 
/// 
protected override bool OnAfterBindModel()
{
    var success = base.OnAfterBindModel();
    int id = 0;
    // Country
    if (Request.Form.AllKeys.Contains(Str.Controllers.Country)
     && int.TryParse(Request.Form[Str.Controllers.Country], out id))
    {
        Model.Item.Country = CountryFacade.GetById(id);
    }
    // Gender
    if (Request.Form.AllKeys.Contains(Str.Controllers.Gender)
     && int.TryParse(Request.Form[Str.Controllers.Gender], out id))
    {
        Model.Item.Gender = GenderFacade.GetById(id);
    }
    return success;
}

/// 
/// Binds non value type properties for searching
/// 
/// 
protected override bool OnAfterBindSearch()
{
    var success = base.OnAfterBindSearch();
    int id;
    // Country
    if (Request.Form.AllKeys.Contains(Str.Controllers.Country)) // there was some selection
    {
        Model.SearchParam.Example.Country = null;               // clear previous to null (it could be final stage also)
        if (int.TryParse(Request.Form[Str.Controllers.Country], out id))
        {
            Model.SearchParam.Example.Country = CountryFacade.GetById(id);

        }
    }
    // Gender
    if (Request.Form.AllKeys.Contains(Str.Controllers.Gender)) // there was some selection
    {
        Model.SearchParam.Example.Gender = null;               // clear previous to null (it could be final stage also)
        if (int.TryParse(Request.Form[Str.Controllers.Gender], out id))
        {
            Model.SearchParam.Example.Gender = GenderFacade.GetById(id);

        }
    }
    return success;
}

As you can see from the code some checks are performed to make sure that a value for Country was provided on the form (in the ascx control) and also to ensure that the supplied value is an integer. Then we call the CountryFacade to find the Country which has the id which was sent from the form and the Country object is returned and added to the Client object.

We also need to add some properties in the properties region:

public override string ControllerName { get { return Str.Controllers.Client; } }
/// 
/// Allowes LAZILY (or via IoC) to work with Country
/// 
public virtual ICountryFacade CountryFacade
{
    protected get
    {
        if (_countryFacade.IsNull())
        {
            _countryFacade = FacadeFactory.CreateFacade(Model.Messages);
        }
        return _countryFacade;
    }
    set
    {
        Check.Require(value.Is(), " ICountryFacade cannot be null");
        _countryFacade = value;
    }
}
/// 
/// Allowes LAZILY (or via IoC) to work with Gender
/// 
public virtual IGenderFacade GenderFacade
{
    protected get
    {
        if (_genderFacade.IsNull())
        {
            _genderFacade = FacadeFactory.CreateFacade(Model.Messages);
        }
        return _genderFacade;
    }
    set
    {
        Check.Require(value.Is(), " IGenderFacade cannot be null");
        _genderFacade = value;
    }
}

This provides a façade for the two entity types which will be used in the OnAfter methods above

The methods above require two local members and these are added in the members region as shown here:

        #region members
        IGenderFacade _genderFacade;
        ICountryFacade _countryFacade;
        #endregion members

Now we have added all the required code to allow us to add a new Client.

New Client Screen

The newly added Client above can be seen in the List view when we click on the Client menu item:

List view with newly added Client entity

Note that Gender and Country do not appear in the list. The properties of the client entity which do appear are the ones which we provided to the Guidance dialogs when we were creating the web infrastructure. As mentioned above the OnList region in the control should be expanded to handle this.

Adding to the List View

In this section we will add to the OnList method in the ClientController to show the Gender and Country of the listed Client entities.

Here is the code which controls what appears in the List:

protected override void OnListToDisplay()
{
    Model.ListModel.ItemsToDisplay = Facade.GetBySearch(Model.SearchParam)
        .Select(i => new ItemToDisplay()
        {
            ID = i.ID,
            Description = i.ToDisplay(),
            Items = new List
            {   
              new HeaderDescription { HeaderName = "Code", Value = i.Code},
              new HeaderDescription { HeaderName = "Name" , Value = i.Name },
              new HeaderDescription { HeaderName = "BankCode" , Value = i.BankCode },
              new HeaderDescription { HeaderName = Str.Common.ID , Value =                                                                                                                                                                                                                    
              i.ID.ToDisplay(), Align = Align.right },
            }
        } as IItemToDisplay);
} 

We will add another line to display the Country Code

new HeaderDescription { HeaderName = Str.Controllers.Country, Value = i.Country.Code, SortByObject=Str.Controllers.Country, SortByProperty=Str.Common.Code},

Column sorting attributes are also provided in this line.

You can choose to display Country.Code, such as ‘IR’ or Country.Display, such as ‘IR (Ireland)’.

The second entity-type property, Gender, is added in a similar way.

Completed List View

It is important to note when working with the Catharsis framework it will often be necessary to rebuild the entire application in order to see changes in the web browser when the application is running in debug mode. This is because of the separation of concerns between the layers of the Catharsis framework. When you make some changes in the code (eg, in the ClientControllerin this case) and press F5 or click the debug button only the files (DLLs) which Visual Studio thinks need to be updated will be updated. Because there is no references exist between the Controller and the web project. This will be explained in more detail later but remember that if you expect to see changes rebuild the entire solution before you test your changes.

Rebuild Solution

Editing Entities

Thanks to the automated guidance no additional coding is required to make the entities editable.

When looking at an entity in the Detail View click the Edit button and the text boxes become editable, change the property that needs to be updated and click Update to save the entity.

Editing an entity

Entity Search

The search function is accessible by clicking the Search button.

Search button

The default search created by the Guidance handles the properties that we provided while setting up the Guidance for the new entity.

Default search screen

The HTML and CSS can be adjusted to suit your needs.

The use of ID, Code, Dame and Bank Code for searching is obvious. The number of rows displayed on the search page can be defined on the search page. It is also possible to display the search results in a new window.

We will now add the code required to search for entity type properties like Country and Gender.

First we will add the elements to the ascx control.

A fieldset containing the comboboxes for the two properties will be added:


This will create the GUI elements that we need and they will be populated with the expected lists.

Completed Search screen

This is enough to allow the system to search through Country and Gender

It is also possible to expand the search functionality to search by Country Name for example, this will be described in a later section.

Using business rules to control how entities are manipulated

Most applications will need some business rules to be employed when manipulating entities. For example if we have a Client who has ‘Germany’ as Country it is not a good idea to allow the system to delete the Country Germany from the available Countries. This would result in a situation whereby an entity uses an entity which no longer exists in the system. This is similar to foreign key data integrity at database level. We do not reply on the database to take care of this, it is more efficient to handle such situations in the code so we will see how this is done now.

Business rules are applied on the business façade which can be found at the location shown here:

Client Facade file location

To enforce a business rule to disallow a Country to be deleted if it is used by a Client we need to get the CountryFacade to ask the ClientFacade if any Clients use the country which we wish to delete.

This involves four steps.

1> The 'CheckDelete' method in the CountryFacade must be overridden and a check should be performed before the deletion of a country is allowed to be processed.

2> The check in the overridden CheckDelete method should call another method in the ClientFacade to check if the Country is in use by a Client.

3> The ClientFacade interface (IClientfacade) should be extended with a method 'IsCountryInUse'

4> The IsCountryInUse method should be implemented in the ClientFacade.

We begin by opening the CountryFacade and adding the following code:

We begin by opening the CountryFacade and adding the following code:

/// 
/// Must check if current entity is not used!
/// if any other entity use this instance we stop deleting and add an error message
/// 
/// 
/// 
protected override bool CheckDelete(Country entity)
{
var result = base.CheckDelete(entity);
      if (ClientFacade.IsCountryInUse(entity))
      {
Messages.AddError(this, Str.Messages.Keys.CannotDeleteItem, Str.Messages.Templates.CannotDelete1
            , entity.ToDisplay());
            result = false;
       }
       return result;
}

This method uses the ClientFacade so we need to add a local member _clientFacade…

#region members
IClientFacade _clientFacade;
#endregion members

We also need a property for ClientFacade:

#region properties
/// 
/// Allowes to set Agent using login
/// 
public virtual IAgentFacade AgentFacade
{
    protected get
    {
        if (_agentFacade.IsNull())
        {
            _agentFacade = FacadeFactory.CreateFacade(Messages);
        }
        return _agentFacade;
    }
    set
    {
        Check.Require(value.Is(), " IAgentFacade cannot be null");
        _agentFacade = value;
    }
}
#endregion properties

The CheckDelete method above calls the method IsCountryInUse and will determine whether the Country can be deleted based on the results of that call.

IsCountryInUse must be added to IClientFacade:

/// 
/// Allows to provide check before delete.
/// Is there any Agent using 'entity' instance as Country
/// 
/// 
/// true if is in use
bool IsCountryInUse(Country entity);
    
Note that it is also necessary to add a using directive so the interface has access to the CodeList namespace because it needs access to the Country object:

using Firm.SmartControls.Entity.CodeLists; 

The above using directive also needs to be added to the ClientFacade.

Now we implement the IsCountryInUse method in the ClientFacade.

#region IClientFacade
/// 
/// Provides checking before a deletion takes place.
/// Are there any Clients using 'entity' instance as Country
/// 
/// 
/// true if is in use
public virtual bool IsCountryInUse(Country entity)
{
    var item = Dao.GetBySearch(new ClientSearch()
    {
        MaxRowsPerPage = 1,
        Example = new Client()
        {
            Country = entity
        }
    }).FirstOrDefault();

    if (item.Is())
    {
        Messages.AddWarning(this, Str.Messages.Keys.ItemInUse, Str.Messages.Templates.ItemIsUsedForEntity3
            , entity.ToDisplay(), item.ToDisplay(), Str.Controllers.Country);
        return true;
    }
    return false;
}
#endregion IClientFacade

Now we can test our code. Run the application and click on Clients to see the list of current clients.

Contries currently used by Clients

We can see that Ireland is in use as a country so now open the CodeLists branch of the navigation tree and click on Country. We can use the red X next to the country Ireland to attempt to delete it. The deletion will fail because of the business rule which we have added. Note that the error messages can be formatted differently if you wish.

Business Rule warning message

Business rules can also me used to control what is allowed during the addition or upgrading on an entity also.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here