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

Catharsis Tutorial 02 - Catharsis Example

0.00/5 (No votes)
26 Jun 2009 1  
See how Catharsis can help you rapidly develop robust enterprise level applications.

Overview

Catharsis is a powerful RAD (Rapid Application Development) tool which uses a collection of best practice Design Patterns built around a solid architecture using MVC. Using Guidance, the framework offers RAD functionality on a robust enterprise level architecture.

Before reading this document, please look at the Catharsis installation guide available here: catharsis_tutorial_01.aspx.

Once you have installed Catharsis, this document serves as a logical next step in gaining knowledge of how this framework can help you build robust applications quickly. When you have read this document, the next one in this series gives you a chance to put the framework into practice by showing you how to add new entities in a simple to follow step by step fashion.

The author of Catharsis, Radim Köhler, has added many CodeProject documents on the Catharsis ASP.NET MVS Framework, which can be found here: http://www.codeproject.com/info/search.aspx?artkw=catharsis&sbo=kw.

This document examines the Firm.SmartControls example solution for the Catharsis framework.

Introduction

The example solution contains entities such as Agent and AgentContract. In this section, we will examine how these entities are integrated into the Catharsis framework.

Let's look at the Insurance Agent entity. First, we look at the database table to see what information is stored for an insurance agent and then we will examine the data layer to see how the table is mapped into the application. Here we see the NHibernate mapping file and the DAO (Data Access Object) in the solution:

catharsis_tutorial_02_001.png

Here is the database table create script:

CREATE TABLE [dbo].[InsuranceAgent](
    [InsuranceAgentId] [int] IDENTITY(1,1) NOT NULL,
    [Code] [nvarchar](150) NOT NULL,
    [IsInternal] [bit] NOT NULL,
    [Rating] [smallint] NOT NULL,
    [CommissionRate] [decimal](18, 10) NOT NULL,
    [CommissionPeak] [decimal](18, 2) NOT NULL,
    [CurrencyId] [smallint] NOT NULL,
    [CountryId] [smallint] NOT NULL,
    [GenderId] [tinyint] NOT NULL,
    [Started] [datetime] NOT NULL,
    [Finished] [datetime] NULL,
 CONSTRAINT [PK_InsuranceAgent] PRIMARY KEY CLUSTERED 
(
    [InsuranceAgentId] ASC
)

Here is the nHibernate mapping file for the InsuranceAgent:

<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
    namespace='Firm.SmartControls.Entity.Insurance' 
    assembly='Firm.SmartControls.Entity'>
  <class name='Agent' table='InsuranceAgent' lazy='true' >
      <id name='ID' column='InsuranceAgentId' >
        <generator class='native'></generator>
      </id>
      <property not-null='true' name='Code' />
      <property not-null='true' name='IsInternal' />
      <property not-null='true' name='Rating' />
      <property not-null='true' name='CommissionRate' />
      <property not-null='true' name='CommissionPeak' />
      <property not-null='true' name='Started' />
      <property not-null='false' name='Finished' />

      <many-to-one name='Currency' column='CurrencyId' lazy='false' ></many-to-one>
      <many-to-one name='Country' column='CountryId' lazy='false' ></many-to-one>
      <many-to-one name='Gender' column='GenderId' lazy='false' ></many-to-one>

      <bag name='Contracts' inverse='true' lazy='true'
               cascade='all-delete-orphan' >
          <key column='InsuranceAgentId' />
          <one-to-many class='AgentContract' />
      </bag>
  </class>
</hibernate-mapping>

Note that lazy="true" means the object will only be instantiated when it is needed.

The first seven parameters mentioned in the mapping table are simple mappings. Currency, Country, and Gender are one to many mappings. These are "Entity-types" rather than simple "Value-types".

They are handled as CodeLists in the Catharsis solution.

catharsis_tutorial_02_002.png

Now we will look at the DAO (Data Access Object). This is the object which reads the information from the database via nHibernate. It is used as part of the searching functionality to return all database rows that match a certain set of criteria. It also takes care of Order-By clauses.

Here is an extract from the file:

public override IList<Agent> GetBySearch(ISearch searchObject)
{
  Check.Require(searchObject.Is(), 
    "Use only AgentSearch not-null instance for searching Agent entities");
  var search = searchObject as AgentSearch;
  if (search.Is())
  {
    Criteria.CreateEqual(Str.Common.ID, search.IDSearch);//Search for ID         
    Criteria.CreateLike(Str.Common.Code, search.Example.Code);
    Criteria.CreateEqual(Str.Business.Agent.IsInternal,    search.IsInternalSearch);
    Criteria.CreateGe(Str.Business.Agent.Rating, search.RatingFrom);
    Criteria.CreateGe(Str.Business.Agent.CommissionRate, search.CommissionRateFrom);
    Criteria.CreateGe(Str.Business.Agent.CommissionPeak, search.CommissionPeakFrom);
    Criteria.CreateGeDate(Str.Business.Agent.Started, search.StartedFrom);
    Criteria.CreateGeDate(Str.Business.Agent.Finished, search.FinishedFrom);
    Criteria.CreateLe(Str.Business.Agent.Rating, search.RatingTo);
    Criteria.CreateLe(Str.Business.Agent.CommissionRate, search.CommissionRateTo);
    Criteria.CreateLe(Str.Business.Agent.CommissionPeak, search.CommissionPeakTo);
    Criteria.CreateLeDate(Str.Business.Agent.Started, search.StartedTo);
    Criteria.CreateLeDate(Str.Business.Agent.Finished, search.FinishedTo);
    Criteria.CreateEqual(Str.Controllers.Currency, search.Example.Currency);
    Criteria.CreateEqual(Str.Controllers.Country, search.Example.Country);
  }
  return GetListByCriteria(searchObject);
}

The Str class is a list of constants which are used instead of strings to avoid errors and to make the code more maintainable. These files can be found at the following location:

catharsis_tutorial_02_003.png

Here is a file extract showing the Agent class:

public partial class Str
{
    public partial class Business
    {
        #region Agent
        public class Agent
        {
            protected Agent() { }
            public const string IsInternal = "IsInternal";
            public const string Rating = "Rating";

The GetBySearch method in the DAO is called using a SearchObject as we can see in the method signature:

public override IList<Agent> GetBySearch(ISearch searchObject)
{
  Check.Require(searchObject.Is(), 
    "Use only AgentSearch not-null instance for searching Agent entities");
  var search = searchObject as AgentSearch;

Now we look at the search object:

catharsis_tutorial_02_004.png

Here is how the search object is set up:

public class AgentSearch : BaseSearch<Agent>, ISearch<Agent>
{
    #region properties
    /// <summary>
    /// Crucial is to provide DIFFERENT name then the EXAMPLE has:
    /// IsInternal -- IsInternalSearch!
    /// Bind methods binds both: Search object + Example!
    /// Example IsInternal is bool not bool? - which could cause demage, 
    /// when null is sent to IsInternal (ment to be IsInternalSearch)
    /// </summary>
    public virtual bool? IsInternalSearch { get; set; }

    public virtual DateTime? StartedFrom { get; set; }
    public virtual DateTime? StartedTo { get; set; }
    public virtual DateTime? FinishedFrom { get; set; }
    public virtual DateTime? FinishedTo { get; set; }

    public virtual short? RatingFrom { get; set; }
    public virtual short? RatingTo { get; set; }

    public virtual decimal? CommissionRateFrom { get; set; }
    public virtual decimal? CommissionRateTo { get; set; }

    public virtual decimal? CommissionPeakFrom { get; set; }
    public virtual decimal? CommissionPeakTo { get; set; }    
    #endregion properties
}

It is important to note that each item in the search can be nullable, as indicated by the question mark after the data type. This means that the search parameters are optional and supplying null for any nullable search parameter is accepted.

Let's look at how the List screen in the application finds and displays the Agents in the database:

The ASPX file which creates the page above is found in the Web project:

catharsis_tutorial_02_006.png

Setting up the grid to display the data is handled by the framework. The framework will automatically create the list. Adding additional columns to the list can be done in the OnBeforeList() method in the Controller of the entity in question.

Here is the constructor in the code-behind List.aspx.cs:

namespace Firm.SmartControls.Web.Views.Agent
{
    public partial class List : ViewPageBase<IAgentModel>
    {
    }
}

We can see that the IAgentModel is used to create the list.

The constructor of this interface is shown here:

[ConcreteType("Firm.SmartControls.Models.Insurance.AgentModel, 
     Firm.SmartControls.Models")]
public interface IAgentModel : IBaseEntityModel<Agent>
{
    /// <summary>
    /// Search parameters for filtering the list of the 'Agent'
    /// </summary>
    new AgentSearch SearchParam { get; set; }
}

You can see that a lot of the code will not need to be changed much in a lot of applications, the framework and the Guidance creates a lot of the files required automatically.

Let's look at the entity used by this interface, the Agent entity.

catharsis_tutorial_02_007.png

This is an important file so we will look at each part of it in detail:

catharsis_tutorial_02_008.png

Members:

#region members
IList<AgentContract> _contracts;
  // NO members for default value - That's done
  // via PROTOTYPE design pattern on Facade.CreateNew()
#endregion members

Each Agent can have many Contracts, this relationship is discussed later.

Some of the code for properties is shown here:

#region properties
/// Unique Code
public virtual string Code { get; set; }
/// IsInternal switch
public virtual bool IsInternal { get; set; }
/// small int as Rating (to show the format abilities)
public virtual short Rating { get; set; }
/// decimal places for counting the commision (to show the format abilities)
public virtual decimal CommissionRate { get; set; }
/// decimal value to keep info about highest Commission (to show the format abilities)
public virtual decimal CommissionPeak { get; set; }
/// codelist
public virtual Currency Currency { get; set; }

Getter and Setter methods exist for each property. Note that the data type for Currency is Currency because another entity is used for this.

This code below is particular to this entity, an Agent can have one or many Contracts.

#region contract list
/// List of contracts (uploaded files)
public virtual IList<AgentContract> Contracts
{
     get 
     {
          if (_contracts.IsNull())
          {
             _contracts = new List<AgentContract>();
             // be lazy as long as possible! 
          }
          return _contracts; 
      }
      protected set { _contracts = value; }
}
#endregion contract list

Every entity in Catharsis has some overwritten abstract methods which can help to identify the entity. The property returned in these methods (code in the example below) can be set while adding an entity using the Catharsis Guidance.

#region override abstract
/// Provides quick, string description about the 'Agent' instance
public override string ToDisplay()
{
    // This method is often used on View
    // there for provide clear and understandable
    // set of properties (separated by spaces ' ')
    // which will quickly provide information about this instance
     return Code;
}
/// Returned value should be handled as business unique key.
/// Equal method (implemented in Tracked base class) will use it 
/// to compare two Agents
protected override string GetDomainObjectSignature()
{
    // TODO return the property (or list of properties separated by "|")
    // which will allow to distinguish among entities
   return Code;
}
#endregion override abstract

The concrete class of the AgentModel looks like this:

/// <summary>
/// All data and binding properties for 'Agent' Views
/// </summary>
public class AgentModel : BaseEntityModel<Agent>, IAgentModel
{
    /// <summary>
    /// Sets ShowExportAction == true (enables the Excel export)
    /// </summary>
    public AgentModel()
       : base()
    {
        ExportModel.ShowExportAction = true;
    }
    /// <summary>
    /// Search object containing Example of this entity
    /// and can be extended with other more powerfull properties for searching
    /// </summary>
    public new AgentSearch SearchParam
    {
       get { return base.SearchParam as AgentSearch; }
       set { base.SearchParam = value; }
     }
}

A practical approach to learning more about how the Catharsis framework works is to actually add new entities and manipulate them, this will be done in the next chapter.

All tutorials in this series can be found here: http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=4013680.

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