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:
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
:
='1.0' ='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.
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); 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:
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:
Here is how the search object is set up:
public class AgentSearch : BaseSearch<Agent>, ISearch<Agent>
{
#region properties
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:
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>
{
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.
This is an important file so we will look at each part of it in detail:
Members:
#region members
IList<AgentContract> _contracts;
#endregion members
Each Agent can have many Contracts, this relationship is discussed later.
Some of the code for properties is shown here:
#region properties
public virtual string Code { get; set; }
public virtual bool IsInternal { get; set; }
public virtual short Rating { get; set; }
public virtual decimal CommissionRate { get; set; }
public virtual decimal CommissionPeak { get; set; }
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
public virtual IList<AgentContract> Contracts
{
get
{
if (_contracts.IsNull())
{
_contracts = new List<AgentContract>();
}
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
public override string ToDisplay()
{
return Code;
}
protected override string GetDomainObjectSignature()
{
return Code;
}
#endregion override abstract
The concrete class of the AgentModel
looks like this:
public class AgentModel : BaseEntityModel<Agent>, IAgentModel
{
public AgentModel()
: base()
{
ExportModel.ShowExportAction = true;
}
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.