Update at 29 Jan 2009
Spring.NET has been chanegd a lot these days, comparing the version I used into this article. Therefore, the attached code is not compatible with the current Spring.NET version. Mark Pollack, the Spring.NET project manager helped me to update the source code for this article. So it's suggested to take his code base as a sample to get the idea how Spring.NET is using NHibernate these days. Here is the codebase Mark has written: here.
Introduction
I have had, professionally, a fair amount of time with Visual C++, Java, and recently, with .NET technologies. I have worked with the popular Spring framework and Hibernate in Java. And now, I have recently discovered that both of these technologies are available in .NET as well. But the Spring framework in Java is more feature rich than Spring.NET. Specifically, I like the integration part of Spring with Hibernate (spring.orm.Jar). That makes the manipulation of data using Hibernate, very easy and in an object oriented manner.
Background
Unlike the Spring framework for Java, Spring.Net has no integration with any ORM technologies. I mean, no ORM integration is found. At least, till now, I have not found anything. For Spring.Net, the declarative transaction demarcation was written by using COM+ services. For those who have a working knowledge with the Spring framework in Java, I think they will not prefer using the COM+ services. COM+ needs advanced knowledge when writing professional software. Moreover, COM+ requires a strong name for the assembly that contains the Serviced Component.
That’s why I did try to implement this integration, so that one can easily write a business layer component without using COM+ services.
I have borrowed the entire concept from the Spring framework for Java, which is an open source framework, while implementing the integration code. I have tried to maintain the same names for the classes and interfaces while implementing. But I have changed many methods with my own code. I have changed the behaviors of some classes too. So I am not claiming that my implementation is an exact copy of the Spring framework for Java. I have just implemented the Spring.Orm.dll as an integration between NHibernate and Spring.Net, like the spring.orm.jar in the Java world.
Article goals
This is my first submission of any kind, so keep non-constructive criticism to a minimum. I am 100% open to feedback, I don't claim to have all the answers, or claim to have the best methods to achieve this or that. Over the next couple in a series of articles, I'd like to demonstrate some n-tier frameworks utilizing NHibernate and Spring.NET.
Getting started
Before starting, I will assume that the reader has some basic knowledge about the following:
- What is NHibernate and how to configure and work with it? Learn from here.
- What is Inversion of Control? Learn from here.
- How to use Spring as an IOC container? Learn from here.
As you might know, NHibernate can be used with any relational database by configuration. On this occasion, I have used the Oracle 9i database as the data sore. Of course, you can change it to any other database by just configuring it in the App.Config file.
From the next section, we will build a middle tier component using Spring.Orm.dll, and we will watch hwo simple it is. And the design is really pluggable and elegant.
Here is the abstract design of our server component:
Step by Step
First of all, start Visual Studio .NET. Create a new console application. Give any name that is legitimate in your business platform. In this tutorial, I am using the name “SpringClient”.
Add references to the following assemblies that are given inside the binary distribution with this article:
- castle.dynamicproxy
- hashcodeprovider
- iesi.collections
- log4net
- nhibernate
- nunit.framework
- spring.aop
- spring.core
And certainly, add a reference to:
Now, add an App.Config file to the project. And write the following:
="1.0"="utf-8"
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context"
type ="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects"
type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<section name="nhibernate"
type="System.Configuration.NameValueSectionHandler, System,
Version=1.0.5000.0,Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<spring>
<context><resource uri="config://spring/objects"/></context>
<objects>
<!—WRITE YOUR SPRING CONFIGURATION HERE-->
</objects>
</spring>
<nhibernate>
<add key="hibernate.show_sql" value="true"/>
<add key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider" />
<add key="hibernate.dialect"
value="NHibernate.Dialect.Oracle9Dialect" />
<add key="hibernate.connection.driver_class"
value="NHibernate.Driver.OracleClientDriver" />
<add key="hibernate.connection.connection_string"
value="Data Source=ihis;User ID=system;Password=manager;" />
</nhibernate>
<log4net>
<appender name="rollingFile"
type="log4net.Appender.ConsoleAppender,log4net" >
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value="yyyy.MM.dd" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern"
value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" />
</layout>
</appender>
<!-- Setup the root Category, add the
appenders and set the default priority -->
<root>
<priority value="DEBUG" />
<appender-ref ref="rollingFile" />
</root>
</log4net>
</configuration>
Tool much XML! Scared?
Don’t. I am explaining. Here in this configuration file, we have just configured the Spring’s IOC container, the NHibernate, and the Log4net for logging purposes.
Find out the comment “WRITE YOUR SPRING CONFIGURATION HERE” inside the XML. And in this area, you will configure the objects that will be loaded by the Spring IOC. If it seems to be too complex to you, don’t just leave. Continue, and you will be able to work it out even without knowing the details of the IOC at the moment. You need to change the DSN (in this example, I have used the DSN name “ihis”), user name, and the password in the database configuration block.
Add an XML file named “hibernate.cfg.xml” to your project. This will be used by NHibernate. Change the build action for this file to Embedded Resource.
Insert the following configuration into the file:
="1.0"="utf-8"
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.0">
<session-factory name="NHibernate.Test"/>
</hibernate-configuration>
Our business logic layer will use the facility of declarative transaction demarcation. So we need a transaction manager. As for this moment, we are using the NHibernate for ORM, and we will use Spring.Orm.Hibernate.HibernateTransactionManager
as the transaction manager from the Spring.Orm assembly.
Let's configure the transaction manager now. Insert the following configuration block to the point which I have marked with the comment:
<object id="myTransactionManager"
type="Spring.Orm.Hibernate.HibernateTransactionManager, Spring.Orm">
<property name="SessionFactory">
<ref object="mySessionFactory"/>
</property>
</object>
For this example, we will implement a single business object for now. I have named it as Product
. So create a class named SpringClient.Utility.Product.UProductDTO
. The suffix DTO stands for “Data Transfer Object”. Insert the following code:
using System;
namespace SpringClient.Utility.Product
{
[Serializable()]
public class UProductDTO
{
public UProductDTO()
{
}
private int versionNumber;
public int VersionNumber
{
get
{
return versionNumber;
}
set
{
versionNumber = value;
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
private long id;
public long ID
{
get
{
return id;
}
set
{
id = value;
}
}
}
}
Now as we will persist the Product
instance into the data store, we need Hibernate mapping a file for this class. Add an XML file into the same namespace named, “UProductDTO.hbm.xml”. And change the Build action of this XML resource to Embedded Resources. The Spring framework always suggest the “”Programming to interface” coding practice. We are not an exception. So let’s write a Data Access Object interface for Product
.
using System;
namespace SpringClient.Utility.Product
{
public interface IUProductDAO
{
UProductDTO Create(UProductDTO Product);
UProductDTO Retrive( long ProductID );
void Update( UProductDTO Product );
void Delete( UProductDTO Product );
UProductDTO[]FindProducts( string queryString ,
object[]Parameters );
}
}
Now we are going to implement the interface:
using System;
using System.Collections;
using Spring.Orm.Hibernate.Support;
namespace SpringClient.Utility.Product
{
public class UProductDAO : HibernateDaoSupport, IUProductDAO
{
public UProductDAO()
{
}
#region IUProductDAO Members
public UProductDTO Create(UProductDTO Product)
{
HibernateTemplate.Save( Product ) ;
return Product;
}
public UProductDTO Retrive( long ProductID )
{
return HibernateTemplate.Load( typeof( UProductDTO ) ,
ProductID ) as UProductDTO;
}
public void Update( UProductDTO Product )
{
HibernateTemplate.Update( Product );
}
public void Delete( UProductDTO Product )
{
HibernateTemplate.Delete( Product );
}
public UProductDTO[]FindProducts( string queryString ,
object[]Parameters )
{
IList resultList =
HibernateTemplate.Find( queryString ,
Parameters ) as IList;
if( null != resultList )
{
UProductDTO[]products =
new UProductDTO[resultList.Count];
resultList.CopyTo( products , 0 );
return products;
}
return null;
}
#endregion
}
}
Note that we have inherited our data access class from Spring.Orm.Hibernate.Support.HibernateDaoSupport
for easy access to the NHibernate methods through the HibernateTemplate
. Now it is time to write the business logic layer. Let’s write an interface for our product's business logic layer:
using System;
namespace SpringClient.Utility.Product
{
public interface IUProductService
{
IUProductDAO ProductDAO
{
get;
set;
}
UProductDTO SaveProduct( UProductDTO Product );
UProductDTO RetriveProduct( long ProductID );
void UpdateProduct( UProductDTO Product );
void DeleteProduct( UProductDTO Product );
UProductDTO[]FindProducts( string queryString ,
object[]Parameters );
}
}
And now I will provide the implementation:
using System;
namespace SpringClient.Utility.Product
{
public class UProductServiceImpl : IUProductService
{
public UProductServiceImpl()
{
}
private IUProductDAO productDAO;
#region IUProductService Members
public IUProductDAO ProductDAO
{
get
{
return productDAO;
}
set
{
productDAO = value;
}
}
public UProductDTO SaveProduct(UProductDTO Product)
{
return productDAO.Create( Product );
}
public UProductDTO RetriveProduct( long ProductID )
{
return productDAO.Retrive( ProductID );
}
public void UpdateProduct( UProductDTO Product )
{
productDAO.Update( Product );
}
public void DeleteProduct( UProductDTO Product )
{
productDAO.Delete( Product );
}
public UProductDTO[]FindProducts( string queryString ,
object[]Parameters )
{
return productDAO.FindProducts( queryString , Parameters );
}
#endregion
}
}
Note that UProductServiceImpl
inherits from no Spring specific class, but this class has the ability to propagate transactions on its methods. It is a crucial difference with COM+. In COM+, your business class must inherit the ServicedComponent
class. At this point, we have completed our business component development. Now, we will configure our business class for the Spring transaction support. Insert the following XML into the AppConfig.xml, just under the transaction manager configuration block that you inserted before a while:
<object id="mySessionFactory"
type="Spring.Orm.Hibernate.LocalSessionFactoryObject, Spring.Orm">
<property name="MappingResources">
<list>
<value>SpringClient.Utility.Product.UProductDTO, SpringClient</value>
</list>
</property>
<property name="ExportSchema" value="true"/>
</object>
Here we are building the Session Factory object. This is simply a proxy of the NHibernate Session factory. The ExportSchema
property is set to true
so that when you will run your server, the database tables that are required will be created for you. Set it to false
to prevent table creation. Now, add the following XML fragments:
<object id="UProductTarget"
type="SpringClient.Utility.Product.UProductServiceImpl, SpringClient">
<property name="ProductDAO">
<ref object="ProductDAO"/>
</property>
</object>
<object id="UProductService"
type="Spring.Transaction.Interceptor.
TransactionProxyFactoryObject, Spring.Orm">
<property name="TransactionManager">
<ref object="myTransactionManager">
</property>
<property name="Target">
<ref object="UProductTarget">
</property>
<property name="TransactionAttributes">
<name-values>
<add key="Save*" value="PROPAGATION_REQUIRES_NEW"/>
<add key="Update*" value="PROPAGATION_REQUIRED"/>
<add key="Delete*" value="PROPAGATION_REQUIRED"/>
</name-values>
</property>
</object>
What is it? First, we are configuring a proxy for our business class that will provide transaction support. Here, we are specifying the transaction propagation as well. We are specifying to Spring that we need a “Requires New” propagation for the “Save
” method and a “Required” propagation mode for the “Update
” and “Delete
” methods. Note that you can specify the method name by using a regular expression.
Now, it is time to think how you are establishing the communication between the client application and your business class? It’s entirely your choice. You can select web services, remoting - any thing you like. For the sake of simplicity, I have implemented the example using .NET Remoting. So, let’s write a class for exposing the remoting objects. This class also plays a role as a façade for the IOC loaded objects.
using System;
using Spring.Context;
using Spring.Context.Support;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
namespace SpringClient.Service
{
public class ServiceFactory
{
private ServiceFactory()
{
}
public static void ExposeServices( params Type[]services)
{
HttpServerChannel channel = new HttpServerChannel(8080);
ChannelServices.RegisterChannel( channel );
foreach( System.Type service in services )
{
System.Runtime.Remoting.RemotingConfiguration.
RegisterWellKnownServiceType(
service , "ProductService",
System.Runtime.Remoting.
WellKnownObjectMode.SingleCall
);
}
}
public static object ProvideService( string serviceName )
{
if( null == m_AppContext )
throw new ApplicationException(
"Spring not initialized yet.", null );
return m_AppContext[serviceName];
}
private static IApplicationContext m_AppContext = null;
public static void BuildSpringContext( )
{
if( null != m_AppContext ) return ;
try
{
m_AppContext = ContextRegistry.GetContext();
if( null != m_AppContext )
{
System.Console.WriteLine("Spring" +
" Initialized Successfully.");
}
}
catch(Exception ex )
{
System.Console.WriteLine( "Failed to" +
" Initialize the Spring context." );
System.Diagnostics.Trace.WriteLine( ex.Message );
}
}
}
}
Let’s write the service class that will expose the business access interface to the extrenal world:
using System;
using System.Web.Services;
using SpringClient.Service;
namespace SpringClient.Utility.Product
{
public class RpcUProductService : MarshalByRefObject
{
public RpcUProductService()
{
}
private string SERVICE_NAME = "UProductService";
public UProductDTO SaveProduct( UProductDTO Product )
{
return ( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).SaveProduct( Product );
}
public UProductDTO RetriveProduct( long ProductID )
{
return ( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).RetriveProduct( ProductID );
}
public void UpdateProduct( UProductDTO Product )
{
( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).UpdateProduct( Product );
}
public void DeleteProduct( UProductDTO Product )
{
( ServiceFactory.ProvideService(SERVICE_NAME)
as IUProductService ).DeleteProduct( Product );
}
public UProductDTO[]FindProducts( string queryString ,
object[]parameters )
{
return ( ServiceFactory.ProvideService( SERVICE_NAME )
as IUProductService ).FindProducts( queryString,
parameters );
}
}
}
Now, change the main method of your application as:
using System;
using SpringClient.Service;
using SpringClient.Utility.Product;
namespace SpringClient
{
public class MainClass
{
[STAThread]
static void Main(string[] args)
{
ServiceFactory.BuildSpringContext();
ServiceFactory.ExposeServices( typeof(
SpringClient.Utility.Product.RpcUProductService) );
System.Console.WriteLine("Service Exported. " +
"Server is running.");
System.Console.ReadLine();
}
}
}
It’s done. You have completed a business tier with declarative transaction facilities. Here, I am providing a screenshot of my project structure.
Writing a client for testing our server
Let’ create a Windows application named RemoteClient. Create a web reference to the remoting component. Select Add Web Reference from the Project menu, and write the following on the URL box: http://localhost:8080/ProductService?WSDL. I am sssuming you are running the server program in the same machine. Otherwise, insert the server machine IP address in place of localhost. Give a name for the service. I have given “ProductService”, for this example.
Now, you can invoke the methods you have written in your product service, as follows:
try
{
RemoteClient.ProductService.RpcUProductServiceService rpc =
new RemoteClient.ProductService.RpcUProductServiceService();
UProductDTO product = new UProductDTO();
product.name = "MyProduct";
rpc.SaveProduct( product );
}
catch(Exception Ex )
{
Console.WriteLine( Ex.Message );
}
That is all. Simple. Isn't it?
Point of interest for further development
The Spring framework for Java provides some more attractive features that I have not implemented in this version, such as save point management, converting Hibernate specific exceptions to Spring's Common Data Access Exceptions, and some other features that are also very important. However, I hope I will implement those parts whenever possible.
Conclusion
I have read and tried to follow the designs from the Spring framework for Java while writing this. But in many areas, I had to break the standard because of language inconsistencies and some other stuff. However, thanks to the Spring framework team.
In order to execute the business component, you must need the assemblies-containing Spring and NHibernate modules. Although I have given these assemblies with this article, you can download these from the following links:
- Download NHibernate related documents from here.
- Download Spring.NET related documents from here.
I hope you will enjoy this article. And I will appreciate all kinds of suggestions and proposals from anyone. Thanks.