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

The Catharsis Demo Project

0.00/5 (No votes)
23 Jun 2009 1  
A guide to understanding the demo solution for the Catharsis ASP.NET MVC Framework

The Catharsis Example Project 

This document gives a brief overview of the Firm.SmartControls example solution for the Catharsis framework. Catharsis is a powerful framework which uses a collection of best practice design patterns built around a solid architecture using MVC.

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.

The source code for Catharsis can be found here.

Introduction 

The demo 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.

NHibernate and DAO files

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 HNibernate 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. The 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.

Codelists

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:

String constants

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

We will now look at this SearchObject.

SearchObject

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:

existing Agent entries - Click to enlarge image

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

List.aspx file

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 codebehind 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 create a lot of the files required automatically.

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

The Agent entity

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

Inside the Agent entity file
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 in a later document.

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 datatype for Currency is ‘Currency’ because another entity is used for this. By this, I mean that the Currency property of Agent is an entity-type of type Currency.

This next code extract 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 powerful properties for searching
    /// </summary>
    public new AgentSearch SearchParam
    {
       get { return base.SearchParam as AgentSearch; }
       set { base.SearchParam = value; }
     }
 }

Summary

This concludes our brief look at the Catharsis example solution. In this document, we have touched on some of the important components of the framework.

A practical approach to learning more about how the Catharsis framework work is to actually add new entities and manipulate them. This will be done in the next chapter, the document is called Extending the Catharsis Demo Project.  

The Next Step 

Now that you have installed Catharsis and examined the demo project, it is a good idea to take a look at the detailed step by step guide on how to add entities to the demo project. With this knowledge, you will very quickly be able to create your own applications.

Read the document here.

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