Introduction
In this article I will be explaining steps involved in developing Microsoft.net web application (ASP.Net C# in this case) based on MyGeneration code generation platform. I will be developing a sample application along the way using a small MS SQL 2008 express database. Although I am using Web application and Microsoft SQL server for this article but the solution is suitable for MySQL and other UI types such as Windows, Console apps. On the top of all I am confident for this approach as I have used this on bigger projects in commercial applications and are happily working.
Part 1 - Understanding Application Requirements
Understanding requirements
This is one of the most important aspects of software development. It’s as important as diagnosing of any illness for a doctor before starting any treatment. Once you get the requirements right the development becomes more visible to you. I am not going to spend whole day on this as it’s a vast area and you can find plenty of articles, books etc on web or in libraries. The main reason to mention this was just to highlight the importance of requirements gathering.
Sample application in this Article - Meeting Room Booking System
Along with the tutorial we will be developing web based application for managing in house Meeting Room bookings within a company. To keep it simple and clean we will not go too fancy on functionality in interest of time and usability.
Requirements of our application
- Web based application accessible over the private intranet network.
- List Rooms , Room types and Room Bookings
- Allow add/Edit/Delete Room, Room types & Bookings
- Store Names & Email addresses of attendees for each booking
- Send reminder and cancellation notifications to attendees
- Since we have assumed that application is only available internally so anyone can access or amend booking details.
Part 2 - MyGeneration Framework
My generation Framework is an open source template driven code generation software. Once you configured your templates according to your requirements then the rest of your application development becomes really easy and you can save valuable time and eliminate human errors. For more details on this framework please follow the links below. It’s really important to understand the framework before you can easily get around the rest of this article.
MyGeneration Download Link
Quick reference guide to MyGeneration Framework
General Tutorials & Articles
Useful MySQL & .NET Articles
I hope you have got a very good Idea after following the above links and article. So In part 3 we will jump onto the business end of programming but before that lets configure the templates to our needs so that we can generate the code we need.
MyGeneration Templates Configuration
I have shipped pre-configured templates that will work with MS SQL 2008 and C#.Net 4.0. Should also work with higher versions of .Net framework but I have not tested with any other .Net framework version. Templates are provided to download at the links above. You should put these unzipped files under C:/Programe Files/Mygeneration13/ so that it will become easier to browse when you work with Mygeneration client. MyGeneration client will look at this directory every time you try to open a template. These template files are saved with .vbgen extension.
For your information I have downloaded these templates from MyGeneration a while ago and modified to make themwork with C#.Net 4.0 and MS SQL 2008. I have added some additional code generation methods such as generic list creation to use with LINQ, Listing column names etc. Feel free to modify them to your needs and please share them with the world if you find any better approach.
Setting Connection Strings in MyGeneration Client
MyGeneration client can be used with multiple connections and you can save as many connections as you like. Just select the one you need to work with and click on save button on very top right corner. This will be the database in current context for creating all the code using required templates.
Part 3 - Application Development
Deciding Application Architecture
In this part we will develop a real world application discussed earlier. Application architecture is a crucial step when it comes to application development. Architecture should be easily understandable by other programmers as well. In this case we have the application problem domain and requirements. We are really developing a very simple application in this case but we will demonstrate the multi-tier approach when we say multi-tier application that means we will be separating out Data, business Logic and presentation layers. In our case we will separate out the application in following layers.
- Data Layer or Database
- Data Access Layer
- Business Logic Layer
- Utility Layer - (Note this layer is not used but included in the source code project)
- GUI Layer (Windows or Web)
- Base Framework – You don’t really need to create this as this is shipped with article. You can either add it as a project reference or direct DLL reference to the projects in your solution.
As stated earlier in this article we will be using MS SQL server 2008 express version as our database engine. (You can use MySQL as well but in that case you will need to use MySQL Templates to generate code as explained later in this article). For sake of simplicity we will keep the Data model very simple. As you can see the model below we will have only four tables to manage our bookings.
- RoomType: This table will store all possible room types within the company.
- Room: Table that holds the room data.
- Booking: This table will contain the data about booking Date & time etc.
- RoomAttendee: Stores attendees details for each booking.
Creating Stored Procedures
We will use MyGen_Template_SQL_StoredProcs.vbgen template to create stored procedures. Open Mygeneration software client and then choose the configured template for creating stored procedures.
Select all the tables under our Database "RoomData" as you can see the image above. Once you have the generated SQL scripts you can then run it directly in MS SQL IDE using Management studio or use Visual stiudio if you prefer to do DB tasks within application platform. In most cases I prefer to directly copy the SQL script from MyGeneration output to MS SQL Management studio and hit F5.
Note: You should never manually modify or rename these stored procedures as these will be recreated when you use this template again and also stored procedures are used as it is in Data Access layer. Should you need any customised SQL scripts in your DB you should clearly isolate them from these auto generated scripts.
Completed Data model should look like similar to the below..
Once you have the Stored procedures created then we have our data model completed for this application. The next step would be to develop application its self.
Visual Studio solution
To Turn the conceptual model into a practical shape lets begin with creating the follwing Projects within the Visual studio solution. As explained earlier in the Achitecture section of this article we will separate out our solution in 5 different layers each layer typically is a project as shown below.
MyGeneration.doodads_2005 - (C#.Net Class Library) The base library shipped with the source. You don’t really need to modify this for any project unless you need to extend it further.
RoomBooking.DAL - (C#.Net Class Library) - Data Access Layer - Project that contains Abstract classes which are auto created by code generation. Please note you must NOT modify these classes directly as code generation will always overwrite them. You must consume these classes in the Business Logic layer using inheritance.
RoomBooking.BLL - (C#.Net Class Library) - Business Logic Layer - Project that contains the classes which are inherited from data Access layer. This is your primary working Area project. Please note these classes will not be overwritten by code generation so you can be fearless to write your custom methods in these classes.
RoomBooking.Util - (C#.Net Library) - Project that contains utility or helper classes such as code for sending email, Read XML file etc. The reason for using this as a separate project is so that it can be consumed by any other project in the solution if needed.
RoomBookingUI - (ASP.Net C# Web Project) - This is our User Interface. We will use ASP.net web application which is provided by Microsoft Visual Studio. The business stuff should happen on the above discussed layers. The interface should only be used for web elements and consume the intelligent work done by other layers. Draw your full attention to make it look good and quick responsive web application.
Data Access Layer
As explained in above part this project will contain the abstract classes. To create these classes open MyGeneration application. Open template by Browse to the templates directory and select MyGen_Template_CSharp_SQL_dOOdads_AbstractClass. Click Run (Play button) or hit F5. Once your connection string is set to the correct database then you will see the dialogue as below.
Output File Path: This must be your DAL project as explained earlier. You can put these classes under sub folders as well if you wish.
Namespace: Namespace for the classes. (Usually a class Library name). It’s up to you to change it but recommended to leave it to the default project name as its the deafult Visual studio behaviour.
Select the database and tables for which you wish to create classes. I recommend to leave the Prefix the file with an underscore ticked as this will clearly isolate abstract classes (Data Access layer) to the Concrete classes (Business Logic Layer). Click ok and you should have the created classes in your project directory. Refresh solution or use add existing items to the project and add these classes manually to the DAL project.
You should not touch any class manually in this project as these will be overwritten when you use the above template again.
If you wish to sneak through the code, Below is the created class for RoomType table. As you can see the class is ready to be consumed with all the required methods & properties available out of the box.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Collections.Specialized;
using MyGeneration.dOOdads;
using System.Collections.Generic;
namespace RoomBooking.DAL
{
public abstract class _RoomType : SqlClientEntity
{
public _RoomType()
{
this.QuerySource = "RoomType";
this.MappingName = "RoomType";
}
public List<string> ColumnsNamesList()
{
List<string> ColList = new List<string>();
ColList.Add("RoomTypeID");
ColList.Add("RoomTypeName");
ColList.Add("RoomTypeDesc");
ColList.Add("Active");
return ColList;
}
public override void AddNew()
{
base.AddNew();
}
public override void FlushData()
{
this._whereClause = null;
this._aggregateClause = null;
base.FlushData();
}
public bool LoadAll()
{
ListDictionary parameters = null;
return base.LoadFromSql("[" + this.SchemaStoredProcedure +
"proc_RoomTypeLoadAll]", parameters);
}
public virtual bool LoadByPrimaryKey(int RoomTypeID)
{
ListDictionary parameters = new ListDictionary();
parameters.Add(Parameters.RoomTypeID, RoomTypeID);
return base.LoadFromSql("[" + this.SchemaStoredProcedure +
"proc_RoomTypeLoadByPrimaryKey]", parameters);
}
#region Parameters
protected class Parameters
{
public static SqlParameter RoomTypeID
{
get
{
return new SqlParameter("@RoomTypeID", SqlDbType.Int, 0);
}
}
public static SqlParameter RoomTypeName
{
get
{
return new SqlParameter("@RoomTypeName", SqlDbType.NVarChar, 50);
}
}
public static SqlParameter RoomTypeDesc
{
get
{
return new SqlParameter("@RoomTypeDesc", SqlDbType.NVarChar, 100);
}
}
public static SqlParameter Active
{
get
{
return new SqlParameter("@Active", SqlDbType.Bit, 0);
}
}
}
#endregion
#region ColumnNames
public class ColumnNames
{
public const string RoomTypeID = "RoomTypeID";
public const string RoomTypeName = "RoomTypeName";
public const string RoomTypeDesc = "RoomTypeDesc";
public const string Active = "Active";
static public string ToPropertyName(string columnName)
{
if (ht == null)
{
ht = new Hashtable();
ht[RoomTypeID] = _RoomType.PropertyNames.RoomTypeID;
ht[RoomTypeName] = _RoomType.PropertyNames.RoomTypeName;
ht[RoomTypeDesc] = _RoomType.PropertyNames.RoomTypeDesc;
ht[Active] = _RoomType.PropertyNames.Active;
}
return (string)ht[columnName];
}
static private Hashtable ht = null;
}
#endregion
#region PropertyNames
public class PropertyNames
{
public const string RoomTypeID = "RoomTypeID";
public const string RoomTypeName = "RoomTypeName";
public const string RoomTypeDesc = "RoomTypeDesc";
public const string Active = "Active";
static public string ToColumnName(string propertyName)
{
if (ht == null)
{
ht = new Hashtable();
ht[RoomTypeID] = _RoomType.ColumnNames.RoomTypeID;
ht[RoomTypeName] = _RoomType.ColumnNames.RoomTypeName;
ht[RoomTypeDesc] = _RoomType.ColumnNames.RoomTypeDesc;
ht[Active] = _RoomType.ColumnNames.Active;
}
return (string)ht[propertyName];
}
static private Hashtable ht = null;
}
#endregion
#region StringPropertyNames
public class StringPropertyNames
{
public const string RoomTypeID = "s_RoomTypeID";
public const string RoomTypeName = "s_RoomTypeName";
public const string RoomTypeDesc = "s_RoomTypeDesc";
public const string Active = "s_Active";
}
#endregion
#region Properties
public virtual int RoomTypeID
{
get
{
return base.Getint(ColumnNames.RoomTypeID);
}
set
{
base.Setint(ColumnNames.RoomTypeID, value);
}
}
public virtual string RoomTypeName
{
get
{
return base.Getstring(ColumnNames.RoomTypeName);
}
set
{
base.Setstring(ColumnNames.RoomTypeName, value);
}
}
public virtual string RoomTypeDesc
{
get
{
return base.Getstring(ColumnNames.RoomTypeDesc);
}
set
{
base.Setstring(ColumnNames.RoomTypeDesc, value);
}
}
public virtual bool Active
{
get
{
return base.Getbool(ColumnNames.Active);
}
set
{
base.Setbool(ColumnNames.Active, value);
}
}
#endregion
#region String Properties
public virtual string s_RoomTypeID
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeID) ?
string.Empty : base.GetintAsString(ColumnNames.RoomTypeID);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeID);
else
this.RoomTypeID = base.SetintAsString(ColumnNames.RoomTypeID, value);
}
}
public virtual string s_RoomTypeName
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeName) ?
string.Empty : base.GetstringAsString(ColumnNames.RoomTypeName);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeName);
else
this.RoomTypeName = base.SetstringAsString(ColumnNames.RoomTypeName, value);
}
}
public virtual string s_RoomTypeDesc
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeDesc) ?
string.Empty : base.GetstringAsString(ColumnNames.RoomTypeDesc);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeDesc);
else
this.RoomTypeDesc = base.SetstringAsString(ColumnNames.RoomTypeDesc, value);
}
}
public virtual string s_Active
{
get
{
return this.IsColumnNull(ColumnNames.Active) ?
string.Empty : base.GetboolAsString(ColumnNames.Active);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.Active);
else
this.Active = base.SetboolAsString(ColumnNames.Active, value);
}
}
#endregion
#region Where Clause
public class WhereClause
{
public WhereClause(BusinessEntity entity)
{
this._entity = entity;
}
public TearOffWhereParameter TearOff
{
get
{
if (_tearOff == null)
{
_tearOff = new TearOffWhereParameter(this);
}
return _tearOff;
}
}
#region WhereParameter TearOffs
public class TearOffWhereParameter
{
public TearOffWhereParameter(WhereClause clause)
{
this._clause = clause;
}
public WhereParameter RoomTypeID
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeID, Parameters.RoomTypeID);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter RoomTypeName
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeName, Parameters.RoomTypeName);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter RoomTypeDesc
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeDesc, Parameters.RoomTypeDesc);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter Active
{
get
{
WhereParameter where = new WhereParameter(ColumnNames.Active, Parameters.Active);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
private WhereClause _clause;
}
#endregion
public WhereParameter RoomTypeID
{
get
{
if (_RoomTypeID_W == null)
{
_RoomTypeID_W = TearOff.RoomTypeID;
}
return _RoomTypeID_W;
}
}
public WhereParameter RoomTypeName
{
get
{
if (_RoomTypeName_W == null)
{
_RoomTypeName_W = TearOff.RoomTypeName;
}
return _RoomTypeName_W;
}
}
public WhereParameter RoomTypeDesc
{
get
{
if (_RoomTypeDesc_W == null)
{
_RoomTypeDesc_W = TearOff.RoomTypeDesc;
}
return _RoomTypeDesc_W;
}
}
public WhereParameter Active
{
get
{
if (_Active_W == null)
{
_Active_W = TearOff.Active;
}
return _Active_W;
}
}
private WhereParameter _RoomTypeID_W = null;
private WhereParameter _RoomTypeName_W = null;
private WhereParameter _RoomTypeDesc_W = null;
private WhereParameter _Active_W = null;
public void WhereClauseReset()
{
_RoomTypeID_W = null;
_RoomTypeName_W = null;
_RoomTypeDesc_W = null;
_Active_W = null;
this._entity.Query.FlushWhereParameters();
}
private BusinessEntity _entity;
private TearOffWhereParameter _tearOff;
}
public WhereClause Where
{
get
{
if (_whereClause == null)
{
_whereClause = new WhereClause(this);
}
return _whereClause;
}
}
private WhereClause _whereClause = null;
#endregion
#region Aggregate Clause
public class AggregateClause
{
public AggregateClause(BusinessEntity entity)
{
this._entity = entity;
}
public TearOffAggregateParameter TearOff
{
get
{
if (_tearOff == null)
{
_tearOff = new TearOffAggregateParameter(this);
}
return _tearOff;
}
}
#region AggregateParameter TearOffs
public class TearOffAggregateParameter
{
public TearOffAggregateParameter(AggregateClause clause)
{
this._clause = clause;
}
public AggregateParameter RoomTypeID
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeID, Parameters.RoomTypeID);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter RoomTypeName
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeName, Parameters.RoomTypeName);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter RoomTypeDesc
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeDesc, Parameters.RoomTypeDesc);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter Active
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.Active, Parameters.Active);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
private AggregateClause _clause;
}
#endregion
public AggregateParameter RoomTypeID
{
get
{
if (_RoomTypeID_W == null)
{
_RoomTypeID_W = TearOff.RoomTypeID;
}
return _RoomTypeID_W;
}
}
public AggregateParameter RoomTypeName
{
get
{
if (_RoomTypeName_W == null)
{
_RoomTypeName_W = TearOff.RoomTypeName;
}
return _RoomTypeName_W;
}
}
public AggregateParameter RoomTypeDesc
{
get
{
if (_RoomTypeDesc_W == null)
{
_RoomTypeDesc_W = TearOff.RoomTypeDesc;
}
return _RoomTypeDesc_W;
}
}
public AggregateParameter Active
{
get
{
if (_Active_W == null)
{
_Active_W = TearOff.Active;
}
return _Active_W;
}
}
private AggregateParameter _RoomTypeID_W = null;
private AggregateParameter _RoomTypeName_W = null;
private AggregateParameter _RoomTypeDesc_W = null;
private AggregateParameter _Active_W = null;
public void AggregateClauseReset()
{
_RoomTypeID_W = null;
_RoomTypeName_W = null;
_RoomTypeDesc_W = null;
_Active_W = null;
this._entity.Query.FlushAggregateParameters();
}
private BusinessEntity _entity;
private TearOffAggregateParameter _tearOff;
}
public AggregateClause Aggregate
{
get
{
if (_aggregateClause == null)
{
_aggregateClause = new AggregateClause(this);
}
return _aggregateClause;
}
}
private AggregateClause _aggregateClause = null;
#endregion
protected override IDbCommand GetInsertCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeInsert]";
CreateParameters(cmd);
SqlParameter p;
p = cmd.Parameters[Parameters.RoomTypeID.ParameterName];
p.Direction = ParameterDirection.Output;
return cmd;
}
protected override IDbCommand GetUpdateCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeUpdate]";
CreateParameters(cmd);
return cmd;
}
protected override IDbCommand GetDeleteCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeDelete]";
SqlParameter p;
p = cmd.Parameters.Add(Parameters.RoomTypeID);
p.SourceColumn = ColumnNames.RoomTypeID;
p.SourceVersion = DataRowVersion.Current;
return cmd;
}
private IDbCommand CreateParameters(SqlCommand cmd)
{
SqlParameter p;
p = cmd.Parameters.Add(Parameters.RoomTypeID);
p.SourceColumn = ColumnNames.RoomTypeID;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.RoomTypeName);
p.SourceColumn = ColumnNames.RoomTypeName;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.RoomTypeDesc);
p.SourceColumn = ColumnNames.RoomTypeDesc;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.Active);
p.SourceColumn = ColumnNames.Active;
p.SourceVersion = DataRowVersion.Current;
return cmd;
}
}
}
Below is the class diagram for Data access layer
Business Logic Layer
Now that you have the Data & Data Access layers done. Next part is the Business Logic where you will actually start write some code by your hand. But to get you started with these classes there's template that will create the classes with some of the out of box methods for you. To create these classes select MyGen_Template_CSharp_SQL_dOOdads_ConcreteClass from the templates directory and then run this template. You will see the dialogue like the one shown below.
Output file path: Use the BAL project directory to place your concrete classes. (Note: This may be very same as the DAL project if you have decided to embed both Data Access & Business Logic layers in a single project.)
Business Namespace: Name space to be used by classes under Business Logic layer. (Usually the project name).
Data Access Namespace: Name space used by the Data Access Layer Classes.
Select Tables and hit OK. Import these created classes into your Business Logic Layer (RoomBooking.BLL) project. If any of the classes exists already this template will not overwrite the old classes. You can extend these classes the way you wish. Below is the example of Class Room i.e. Room Table in our database. Note the inheritance from its Data access counterpart _Room. Also Note the custom method that I have manually wrote in this class to get the RoomType object for this class.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using MyGeneration.dOOdads;
using RoomBooking.DAL;
namespace RoomBooking.BLL
{
public class Room : _Room
{
public enum SearchType
{
Equal,
StartsWith,
EndsWith,
Wild,
}
public enum SortDirection
{
Asc,
Desc,
}
public Room(string ConnectionString)
{
this.ConnectionString = ConnectionString;
}
public bool LoadAll(bool ApplySort)
{
bool _Loadall = true;
_Loadall = this.LoadAll();
if (_Loadall && ApplySort)
{
this.Sort = Room.ColumnNames.RoomName;
}
return _Loadall;
}
public bool LoadByColumnValue(string ColumnName, string ColumnValue, SearchType SearchTypeOption)
{
bool _Loadall = true;
_Loadall = this.LoadAll();
if (_Loadall)
{
switch (SearchTypeOption)
{
case SearchType.Equal:
this.Filter = ColumnName + "='" + ColumnValue + "'";
return _Loadall;
case SearchType.StartsWith:
this.Filter = ColumnName + " Like '%" + ColumnValue + "'";
return _Loadall;
case SearchType.EndsWith:
this.Filter = ColumnName + " Like '" + ColumnValue + "%'";
return _Loadall;
case SearchType.Wild:
this.Filter = ColumnName + " Like '%" + ColumnValue + "%'";
return _Loadall;
}
}
return _Loadall;
}
public List<Room> ConvertToList(Room InputObject)
{
List<Room> List = new List<Room>();
List<DataRow> dlist = new List<DataRow>();
dlist = InputObject.DefaultView.ToTable().AsEnumerable().ToList<DataRow>();
foreach (DataRow row in dlist)
{
Room InsObject = new Room(this.ConnectionString);
InsObject.AddNew();
DataRowToObject(InsObject, row);
List.Add(InsObject);
}
return List;
}
public List<Room> ConvertToList(Room InputObject,
string sortcolumn, SortDirection sortDir, int page, int numRows)
{
List<Room> List = new List<Room>();
if (sortcolumn != null)
InputObject.Sort = string.Format("{0} {1}", sortcolumn, sortDir.ToString());
List = ConvertToList(InputObject);
return List.AsQueryable().Skip((page - 1) * numRows).Take(numRows).ToList<Room>();
}
public RoomType GetRoomType()
{
RoomType RoomType = new RoomType(this.ConnectionString);
RoomType.LoadByPrimaryKey(this.RoomTypeID);
return RoomType;
}
}
}
Below is the class diagram for Business Logic layer
Utility Layer
This project is there for convenience and to keep the things simple we have not used in this example. You can include any reusable method you wish in this project for example sending email code we could have placed here.
UI Layer - ASP.NET Web application
At this stage you are a web designer more than an application developer. In a bigger team this part should have been done in parallel to the above layers. The design concept in normal cases will be agreed in advance with the end users. Anyway let’s assume you are the only one working and nothing has been done yet for this apart from the requirements.
NOTE: You can use any methodology or approach for designing the web application such as MVC etc. For this article we will stick to the usual Microsoft ASP.NET web application using C#.Net.
I am a big fan of user controls when it comes to web application development. Since it’s a very simple application we only need three major parts i.e. Room, RoomType and Bookings (Thinking bjects here). So as you can assume Room & Rooms types are usual list and edit stuff. So let’s start with listing & editing RoomTypes.
Listing/Editing RoomTypes: For listing RoomTypes we will use Datagridview on a web form. On load this page will display all the room types available in the system. To achieve this by code we will use the List object of Business Logic layer i.e. List of RoomTypes and simply bind it to the Gridview. See the code snippet below...
private void LoadRoomTypeList()
{
RoomType RoomType = new RoomType(AppGlobals.ConnectionStrings.cstrRoomData);
RoomType.LoadAll(true);
gvRoomType.DataSource = RoomType.DefaultView;
gvRoomType.DataBind();
}
Now by looking at the above code the obvious question you would have that what is "Appglobals.ConnectionStrings.ctrRoomdata". It’s simply a connection string that is passed to the business object. Each business object created in Business Logic layer takes this as its initialization parameter. AppGloabls is a static class used for storing such static values to be used in the application. You can imagine how many times we have to use this connection strings and it may not be a good idea to hard code it everywhere.
Below is the list of Room types displayed on a web form.
To edit we will use the user control and make it available on the page as a pop up using Ajax Popup extender. User control is simply a tabular form. Note that this user control raises two custom events SaveClicked and cancelClicked. This is done in order to handle Ajax popup extender since popup extender can only be controlled (at least easily) from the page where it was created.
Handling popup extender from user control: Every time user clicks on Save or Cancel, we need to hide the popup and show the main page. We will create two custom events on user Control and handle them on the main page.
public EventHandler SaveClicked;
public EventHandler CancelClicked;
protected void btnSave_Click(object sender, EventArgs e)
{
Validate();
RoomType RoomType = new RoomType(AppGlobals.ConnectionStrings.cstrRoomData);
if (Page.IsValid)
{
if (lblRoomTypeID.Text == "0")
{
RoomType.AddNew();
RoomType.Active = true;
RoomType.RoomTypeName = txtRoomTypeName.Text;
RoomType.RoomTypeDesc = txtRoomTypeDesc.Text;
RoomType.RoomTypeID = 0;
RoomType.Save();
}
else
{
RoomType.LoadByPrimaryKey(Convert.ToInt32(lblRoomTypeID.Text));
if (RoomType.RoomTypeDesc != txtRoomTypeDesc.Text ||
RoomType.RoomTypeName != txtRoomTypeName.Text)
{
RoomType.RoomTypeName = txtRoomTypeName.Text;
RoomType.RoomTypeDesc = txtRoomTypeDesc.Text;
RoomType.Save();
}
}
SaveClicked(sender, e);
}
else
{
}
}
protected void btnCancel_Click(object sender, EventArgs e)
{
CancelClicked(sender, e);
}
On Web form we will then register these events as below.
ucRoomTypeEdit1.SaveClicked += new EventHandler(ucRoomTypeEdit1_SaveClicked);
ucRoomTypeEdit1.CancelClicked += new EventHandler(ucRoomTypeEdit1_CancelClicked);
And then handle them like this to Show/Hide the Edit popup on the web form. Please Note that this approach is used throughout the application.
protected void ucRoomTypeEdit1_SaveClicked(object sender, EventArgs e)
{
pnlRoomTypeEdit.Visible = false;
lblInfo.Visible = false;
MPE.Hide();
LoadRoomTypeList();
}
protected void ucRoomTypeEdit1_CancelClicked(object sender, EventArgs e)
{
pnlRoomTypeEdit.Visible = false;
lblInfo.Visible = false;
MPE.Hide();
}
Listing/Editing Rooms: We will save time here as it’s not needed to explain it again. This is very same as RoomType apart from the object is different.
Listing/Editing Bookings: The most important part of our application’s web layer. We need to provide an easy and efficient way to handle bookings. If you list the bookings like the above objects it’s not really a user friendly way to show the bookings. It’s a bit of challenge to develop some custom control to handle all this. So why not use Google to find something which can handle all this nice features. After spending not too long but good few hours I came up with the following that can fit well into our requirements.
http://www.daypilot.org/calendar.html
For the purpose of this article I have used the full featured trial version of Daypilot calendar control. This gives us the out of box functionality to handle on screen edit, delete and create Calendar entries and provides a nice look to the end users. Also you can style the control in your own way.
Databindings are very same as the gridview. We will split the booking display per room so that user can select he room and date range to find or create the appropriate bookings. Below is the code snippet to bind the bookings data to calendar control.
int RoomID = ddlRoom.SelectedValue.Length > 0 ? Convert.ToInt32(ddlRoom.SelectedValue) : 0;
int Days = Convert.ToInt32(rdWeekView.SelectedValue) * 5;
Booking Booking = new Booking(AppGlobals.ConnectionStrings.cstrRoomData);
DayPilotCalendar1.Days = Days;
Booking.LoadByColumnValue(Booking.ColumnNames.RoomID, RoomID.ToString(), RoomBooking.BLL.Booking.SearchType.Equal);
DayPilotCalendar1.DataSource = Booking.DefaultView;
DayPilotCalendar1.DataBind();
DayPilotCalendar1.UpdateWithMessage(LoadingMessage);
Above is the completed booking form. Calendar control server side events are used to handle booking Creation, deletion and modification. Few code snippets below from this form.
Whenever user selects an existing booking we need to popup the booking edit form.
protected void DayPilotCalendar1_EventSelect(object sender, DayPilotEventArgs e)
{
int SelectedBookingID = Convert.ToInt32(DayPilotCalendar1.SelectedEvents[0].Value);
MPE.Show();
pnlBookingEdit.Visible = true;
ucBookingEdit1.LoadData(SelectedBookingID);
}
Apart from creating a booking from new button user also has an option to drag the mouse along the cells and we need to handle this to create the new booking. Below is the code to handle TimeRangeSelected event and show the new Booking form in a popup. Note we prevent past date bookings.
protected void DayPilotCalendar1_TimeRangeSelected(object sender, TimeRangeSelectedEventArgs e)
{
if (e.Start.Date > DateTime.Today.AddDays(-1))
{
MPE.Show();
pnlBookingEdit.Visible = true;
ucBookingEdit1.CreateNew(Convert.ToInt32(ddlRoom.SelectedValue), e.Start, e.End, false);
}
else
{
LoadBookings("New booking for Past date not possible");
}
}
Handling Attendees and Notify Meeting Cancellations & Reschedules: Attendees are tied to each booking. As you can see these are handled in the booking edit form. Every time booking is Created, Moved or Deleted attendees are notified by sending an email. Please download the source code for full details. Below is the code for sending emails and that is in AppGlobals static class in the web application.
public static void SendMeetingEmail(Room Room, Booking RoomBooking,
List<RoomAttendee> RoomAttendeeList, string MeetingAction)
{
int ToEmailCount;
int DocCount;
string _EmailRecepients = string.Empty;
foreach (RoomAttendee roomAttendee in RoomAttendeeList)
{
if (_EmailRecepients.Length > 0)
_EmailRecepients = string.Format("{0};{1}", _EmailRecepients, roomAttendee.Email);
else
_EmailRecepients = roomAttendee.Email;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Hi, <br/><br/> Please Note - " +
"Meeting detailed below has been <b>{0}</b> by {1}",
MeetingAction, RoomBooking.BookedBy));
sb.AppendLine("<br/><br/>");
sb.AppendLine(string.Format("Description: {0}",
RoomBooking.BookingNotes.Remove(RoomBooking.BookingNotes.LastIndexOf("-"))));
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("Location: {0}", Room.RoomName));
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("Start Time: {0}", RoomBooking.BookDateFrom));
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("End Time: {0}", RoomBooking.BookDateTo));
sb.AppendLine("<br/>");
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("People in this meeting : {0}", RoomAttendeeList.Count));
sb.AppendLine("<table border='1'>");
foreach (RoomAttendee roomAttendee in RoomAttendeeList)
{
sb.AppendLine(string.Format("<tr><td>{0} {1} {2}" +
"</td></tr>", roomAttendee.Title,
roomAttendee.FirstName, roomAttendee.LastName));
}
sb.AppendLine("</table>");
sb.AppendLine("<br/>");
sb.AppendLine("<br/>");
sb.AppendLine("<i>System generated email, Please DO NOT Reply !!</i>");
sb.AppendLine("<br/><br/>");
sb.AppendLine("Best Regards,");
sb.AppendLine("<br/>");
sb.AppendLine("Admin");
sb.AppendLine("<br/>");
sb.AppendLine("Meeting Room Booking System");
if (_EmailRecepients.Trim().Length > 0)
{
SendEmail(_EmailRecepients,
string.Format("Meeting Room Booking System - Meeting {0}", MeetingAction),
sb.ToString(),
string.Empty,
out ToEmailCount,
out DocCount);
}
}
Part 4 - My2ndGeneration: Future code generation
MyGeneration has recently gone web based. I haven’t had a chance to look at this in details. It looks like the future of code generation. More details on this link.
As soon as I get my head around this new platform I will post another article here So stay in touch...!!
Conclusion
To sum up this is one of the many available approaches to develop object oriented application. As any new platform takes time to learn MyGerenation platform is no different. It needs some of your time to get your head around it. But for sure this is practical and I have used it in many projects. It has saved us a lot of development time and the power of templates has made it even more interesting. Please do get back with any Feedback and vote for this article if you think it can be of any use to the world out there.
Happy coding...!!