2LeggedSpider

Container-Managed Persistence

Posted in ASP.NET, C# by Sumit Thomas on February 28, 2006

[tweetmeme style=”compact”]The most fascinating part in developing ASP.NET applications for me is how you can leverage different design models relatively easily, compared to other platforms. Coming from a ASP background, I was excited when I created my first 3-tier application in ASP.NET. I’ve been interested in architectures ever since. The n-tier architecture adopted in most of the Microsoft’s sample applications such as IBuySpy, is one of the commonly used models and I’ve used it successfully in my applications. It is what we call a Component-Managed Persistence model, where the component needs to know how to make calls to the persistence layer such as SQL Server and also what are the parameters required for a transaction with the persistence layer. So in a sense the business layer is tightly coupled with the under lying persistence layer.

I came across this book “ASP.NET E-commerce Programming: Problem – Design – Solution” by Kevin Hoffman a long time back. Though I bought the book, i really didn’t understand the Container-Managed Persistence model described in the book. I had tough time trying to download the code associated with the book as the book was no longer supported on www.wrox.com. Anyway, I managed to download the code after several hours of surfing and googling the web. I spent a lot od time trying to understand how the architecture works and I finally managed to understand how brilliant it is. I was however surprised to find that there ain’t much reviews or talk about this book anywhere. Anyway, I would like to thank Kevin Hoffman for this wonderful book.

So what is Container-Managed Persistence?

Quoting from the book: “Container-Managed Persistence is a design pattern whereby business objects have no direct knowledge of where their data came from and how it will be persisted” The business objects are pure business objects and are never tighly coupled with any persistence layer.

After trying several sample applications with the CMPServices library and the associated libraries that formed the part of the architecture, there was one issue that I wasn’t comfortable with in the CMPServices library. The PersistableObjectSet.cs class uses an internal DataSet for handling the resultset from the database. Now, I being a strong advocate of using DataReaders in ASP.NET application was not really comfortable with it. So, I decided to try my hands on tweaking the CMPServices library to make DataReader as the default data handling object and also provide the developer with an option to choose the data object – DataSet, DataTable or DataReader he/she wants to use in the application.

I made few additions to the CMPServices library to achieve what I said. Now to start off I created an Enum CMPDataObjectType as follows

public enum CMPDataObjectType
	{
		/// 
		/// DataSet
		/// 
		DataSet = 1,
		/// 
		/// DataTable
		/// 
		DataTable = 2,
		/// 
		/// DataReader
		/// 
		DataReader = 3
	}

The enum has three fields, representing the Data object types that we are going to support in the CMPServices library. The actual PerisistableObjectSet class in the CMPServices library is as follows

public class PersistableObjectSet : PersistableObject
	{
		protected DataSet internalData;

		public PersistableObjectSet()
		{
			internalData = new DataSet();	
		}

		public virtual void FinalizeData()
		{
			PersistableObjectSet.FinalizeData( internalData );
		}

		public static void FinalizeData(DataSet scratchData )
		{

		}

		public DataSet ResultSet
		{
			get 
			{
				return internalData;
			}
			set 
			{
				internalData = value;
			}
		}
	}

As you can see it has an internal DataSet which is used to hold the records returned from the database. Now we need to add support for DataReader and DataTable in this class. So I modified the class as follows

public class PersistableObjectSet : PersistableObject
	{

		/// 
		/// The internal DataSet of the PersistableObjectSet
		/// 
		protected DataSet objectDataSet;
		/// 
		/// The internal DataReader of the PersistableObjectSet
		/// 
		protected IDataReader objectDataReader;
		/// 
		/// The internal DataTable of the PersistableObjectSet
		/// 
		protected DataTable objectDataTable;

		/// 
		/// CMPDataObjectType
		/// 
		protected CMPDataObjectType cmpDataObjectType;

		/// 
		/// Default constructor
		/// 
		public PersistableObjectSet()
		{
			
		}

		/// 
		/// Gets/Sets the value of the internal DataSet of the PersistableObjectSet
		/// 
		public DataSet DataSet
		{
			get
			{
				return objectDataSet;
			}
			set
			{
				objectDataSet = value;
			}
		}

		/// 
		/// Gets/Sets the value of the internal DataReader of the PersistableObjectSet
		/// 
		public IDataReader DataReader
		{
			get
			{
				return objectDataReader;
			}
			set
			{
				objectDataReader = value;
			}
		}


		/// 
		/// Gets/Sets the value of the internal DataTable of the PersistableObjectSet
		/// 
		public DataTable DataTable
		{
			get
			{
				return objectDataTable;
			}
			set
			{
				objectDataTable = value;
			}
		}

		/// 
		/// The CMPDataObjectType refers to the Datatype that will be used for Data retrieval.
		/// There are 3 CMPDataObjectTypes - DataSet, DataTable and DataReader.
		/// DataReader is the default one and it is recommended to use it for any kind of data retrieval.
		/// DataTable and DataSet must be used only for special data maniuplations.
		/// DataSet should be used only for special requirements like serialization/deserialization, caching etc.
		/// 
		public CMPDataObjectType DataObjectType
		{
			get
			{
				if( cmpDataObjectType == 0 )
				{
					cmpDataObjectType = CMPDataObjectType.DataReader;
				}
				return cmpDataObjectType;
			}
			set
			{
				cmpDataObjectType = value;
			}
		}

		/// 
		/// This method provides a format for child classes to implement data manipulation methods.
		/// If any additional work needs to be done on the retrieved data, this method can be 
		/// implemented as a standard place for data finalisation to take place.
		/// 
		/// If you are using a DataReader you SHOULD call this method in your business logic to
		/// close the Connection object.
		/// 
		public virtual void FinaliseData()
		{
			if( cmpDataObjectType == CMPDataObjectType.DataSet )
			{
				PersistableObjectSet.FinaliseData( objectDataSet );
			}
			else if( cmpDataObjectType == CMPDataObjectType.DataReader )
			{
				PersistableObjectSet.FinaliseData( objectDataReader );
			}
			else if( cmpDataObjectType == CMPDataObjectType.DataTable )
			{
				PersistableObjectSet.FinaliseData( objectDataTable );
			}
		}

		/// 
		/// This method is called by the FinaliseData() overload in the business logic to do
		/// any additional work with the DataSet. 
		/// 
		/// DataSet
		public static void FinaliseData( DataSet tempData )
		{
			tempData.Dispose();
		}

		/// 
		/// This method is called by the FinaliseData() overload in the business logic to do
		/// any additional work with the DataTable. 
		/// 
		/// DataTable
		public static void FinaliseData( DataTable tempData )
		{
			tempData.Dispose();
		}

		/// 
		/// This method is called by the FinaliseData() overload in the business logic to do
		/// any additional work with the DataReader. 
		/// 
		/// IDataReader/SqlDataReader
		public static void FinaliseData( IDataReader tempData )
		{
			tempData.Close();
			tempData.Dispose();
		}

	}

So basically I have added a property DataObjectType which is used to set the type of data object we are going to use. By default it is DataReader. Each data types will have their own FinaliseData method. For the DataReader we make sure that the object is closed in order to close its connection object.

We also need to do couple of changes to the SqlPersistenceContainer class, as we now have three types of data object types to handle.

Since we use the data object only while retrieving the data, we modify the Select method as follows

		/// 
		/// This method performs the Select operation. It executes a stored procedure and places any return or output values
		/// back onto the instance of the PersistableObject that was provided. If the object instance can be cast to a PersistableObjectSet, then the
		/// container will attempt to assign the output or return value to a Data object within the PersistableObjectSet.
		/// 
		/// Persistable object
		public override void Select( PersistableObject selectObject )
		{
			try
			{
				CommandMapping commandMap	= containerMap.SelectCommand;
				SqlCommand selectCommand	= BuildCommandFromMapping( commandMap );
				AssignValuesToParameters( commandMap, ref selectCommand, selectObject );
				if(selectCommand.Connection.State == ConnectionState.Closed)
					selectCommand.Connection.Open();

				if( selectObject is PersistableObjectSet )
				{
					PersistableObjectSet objectSet = (PersistableObjectSet)selectObject;
					
					AssignResultSetToObjectSet( commandMap, selectCommand, ref selectObject );
					AssignOutputValuesToInstance( commandMap, selectCommand, ref selectObject );
					//selectCommand.Connection.Close();
				}
				else
				{
					selectCommand.ExecuteNonQuery();
					selectCommand.Connection.Close();
					AssignOutputValuesToInstance( commandMap, selectCommand, ref selectObject );
				}
				//selectCommand.Connection.Dispose();
				//selectCommand.Dispose();
			}
			catch (Exception dbException)
			{
				throw new Exception("Persistance (Select) Failed for PersistableObject", dbException );
			}
		}

We have remaned AssignResultSetToDataSet to AssignResultSetToObjectSet to give it a more generic name. The AssignResultSetToObjectSet method is as follows

private void AssignResultSetToObjectSet( CommandMapping commandMap, SqlCommand sqlCommand, ref PersistableObject persistObject )
		{
			SqlDataAdapter sqlDa = null;
			PersistableObjectSet objectSet = (PersistableObjectSet)persistObject;
			if( objectSet.DataObjectType == CMPDataObjectType.DataSet )
			{
				sqlDa = new SqlDataAdapter( sqlCommand );
				objectSet.DataSet = new DataSet();
				sqlDa.Fill ( objectSet.DataSet );
				sqlCommand.Connection.Close();
				sqlCommand.Connection.Dispose();
				sqlCommand.Dispose();
			}
			else if( objectSet.DataObjectType == CMPDataObjectType.DataReader )
			{
				SqlDataReader objReader = sqlCommand.ExecuteReader( CommandBehavior.CloseConnection );
				objectSet.DataReader = objReader;
			}
			else if( objectSet.DataObjectType == CMPDataObjectType.DataTable )
			{
				sqlDa = new SqlDataAdapter( sqlCommand );
				objectSet.DataTable = new DataTable();
				sqlDa.Fill ( objectSet.DataTable );
				sqlCommand.Connection.Close();
				sqlCommand.Connection.Dispose();
				sqlCommand.Dispose();
			}
		}

We create the instance of the select data object type and fetch the data using it. So, thats all the changes we do to the CMPServices library.

Now lets see how we use it. I created a simple contacts management application and here is a sample of a function in the Business layer. Since I am sure I am not going to change the underlying datasource, I populate the data to a strongly-typed collection object which I will then use in my presentation layer. I have a function called GetContacts and I have three versions of the function to support the data object type under consideration.

For DataReader (default)

public static Contacts GetContacts()
            {
                  SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] );
                  ContactSet contactSet         = new ContactSet();

                  spc.Select( contactSet );

                  Contacts contactList         = new Contacts();

                  while( contactSet.DataReader.Read() )
                  {
                        IDataReader row               = contactSet.DataReader;
                        Contact contact               = new Contact();
                        contact.ContactId             = Convert.ToInt32( row["ContactId"] );
                        contact.ContactName           = row["ContactName"].ToString();
                        contact.Email                 = row["Email"].ToString();

                        contactList.Add( contact );
                  }

                  ///It is imperative that you call the FinaliseData method for DataReader as this will
                  ///automatically close it’s connection object. Note that it is called after the DataReader's values
                  ///are retrieved as DataReader's require the connection object to be open unlike DataSet/DataTable.

                  contactSet.FinaliseData();
                  return contactList;
            }

For DataSet

	    public static Contacts GetContacts()
            {
                  SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] );
                  ContactSet contactSet         = new ContactSet();

                  //Setting the DataObjectType of the contactSet object to DataSet
                  contactSet.DataObjectType     = CMPDataObjectType.DataSet;

                  spc.Select( contactSet );

                  Contacts contactList         = new Contacts();

                  ///For DataSet and DataTable the FinaliseData method should be called before extracting the values
                  ///as the manipulation with the data would have occured if there is an overriden FinaliseData method 
                  ///in the ContactSet class

                  contactSet.FinaliseData();

                  foreach( DataRow row in contactSet.DataSet.Tables[0].Rows )
                  {
                        Contact contact               = new Contact();
                        contact.ContactId             = Convert.ToInt32( row["ContactId"] );
                        contact.ContactName           = row["ContactName"].ToString();
                        contact.Email                 = row["Email"].ToString();

	                contactList.Add( contact );
                  }

                  return contactList;
            }

For DataTable

	  public static Contacts GetContacts()
         {
                 SqlPersistenceContainer spc = new SqlPersistenceContainer( CMPConfigurationHandler.ContainerMaps["SherstonContacts"] );
                  ContactSet contactSet         = new ContactSet();

                  //Setting the DataObjectType of the contactSet object to DataTable
                  contactSet.DataObjectType     = CMPDataObjectType.DataTable;

                  spc.Select( contactSet );

                  Contacts contactList         = new Contacts();


                  ///For DataSet and DataTable the FinaliseData method should be called before extracting the values
                  ///as the manipulation with the data would have occured if there is an overriden FinaliseData method 
                  ///in the ContactSet class

                  contactSet.FinaliseData();

                  foreach( DataRow row in contactSet.DataTable.Rows )
                  {
                        Contact contact               = new Contact();
                        contact.ContactId             = Convert.ToInt32( row["ContactId"] );
                        contact.ContactName           = row["ContactName"].ToString();
                        contact.Email                 = row["Email"].ToString();

                        contactList.Add( contact );
                  }

                  return contactList;

            }

So here it is a modified CMPServices library with support for DataReader and DataTable 🙂

Technorati: , ,

Displaying RSS feeds in ASP.NET

Posted in ASP.NET by Sumit Thomas on February 22, 2006

[tweetmeme style=”compact”]The XML control is one of the least used controls in ASP.NET. To be honest I haven’t used it much myself. I combined my little knowledge in XSL with the XML control to create a simple RSS reader. Here is the code for the WebForm

.rssContainer
{
border: 1px dotted #000000;
padding: 3px;
font-family: Arial, Verdana, Geneva;
font-size: 11px;
width: 100%;
}
#header
{
background-color: #f7f7f7;
border: 1px solid #000000;
}
#header h2
{
color: #333333;
}
#header .description
{
font-size: 14px;
font-weight: bold;
}



void Page_Load(object source, System.EventArgs e)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("https://2leggedspider.wordpress.com/feed/");
rssFeed.Document = xmlDoc;
rssFeed.TransformSource = rss.xsl;
}




    
    
    



The code as you can see is very simple. We create a XmlDocument object and open the feed using it. Then we pass the document to the XML control. You would also notice that we are passing an XSL source to the XML control through it’s TransformSource property. The XSL does all the formatting of the feed data. Lets look at the XSL code now.








<table class="rssContainer">
<tr>
<td>
<h2>
<a href="{link}" target="_blank">
</a>
</h2>
<div class="description">
</div>
</td>
</tr>
<tr>
<td>

</td>
</tr>
</table>


<a href="{link}" target="_blank"></a>  - 
  

<br /><i>
</i>
<hr />




The XSL code is pretty simple as you can see. Well I definetely will consider this option when I need to display any kind of XML content again.

Technorati: , ,
Tagged with: ,

Edit Web.config at run-time

Posted in ASP.NET, C# by Sumit Thomas on February 21, 2006

[tweetmeme style=”compact”]I had an argument with one of my friends recently on his statement that Web.config cannot be edited at run-time. My counter statement was if it is a XML file and if you have write permission on it then you can do it. Here is the code to do the same…

public class ConfigManager
{
 //The file path of Web.config
 static string configFilePath =
 HttpContext.Current.Server.MapPath("~/web.config");
  
 /// Returns the Web.config as XmlDocument
 private static XmlDocument GetWebConfig()
 {
 XmlDocument xmlDoc = new XmlDocument();
 xmlDoc.Load( configFilePath );
 return xmlDoc;
 }

 /// Checks for the appSettings node in Web.config
 /// Updates the value for the given key if the key
 /// already exists or creates a new node for the
 /// given key and value combination if it is not present
 public static void CreateAppSetting( string key, string val )
 {
 XmlDocument xmlDoc = GetWebConfig();
 XmlNode xmlNode = xmlDoc.SelectSingleNode("//appSettings");
   
 if( xmlNode == null )
 {
  throw new Exception("appSettings node not found!");
 }

 string nodeFormat = string.Format("//add[@key='{0}']", key) );

 XmlElement xmlElement =
( (XmlElement) xmlNode.SelectSingleNode( nodeFormat );
 try
 {   
  if( xmlElement != null )
  {
   xmlElement.SetAttribute( "value", val );
  }
  else
  {
   xmlElement = xmlDoc.CreateElement( "add" );
   xmlElement.SetAttribute( "key", key );
   xmlElement.SetAttribute( "value", val );
   xmlNode.AppendChild( xmlElement );
  }
  SaveWebConfig( xmlDoc );
 }
 catch( Exception ex )
 {
  throw new Exception( ex.Message );
 }

 }

 /// Saves the changes to the Web.config file
 private static void SaveWebConfig( XmlDocument xmlDoc )
 {
  try
  {
  XmlTextWriter writer =
 new XmlTextWriter( configFilePath, null );
  writer.Formatting = Formatting.Indented;
  xmlDoc.WriteTo( writer );
  writer.Flush();
  writer.Close();
  }
  catch( Exception ex )
  {
  throw new Exception( ex.Message );
  }
 }
}//end of class

The application should have required permissions on Web.config to edit it, otherwise the code will throw an access denied error.

Technorati: , ,
Tagged with: ,