Tuesday, July 28, 2009

Using NHibernate and Log4Net in ASP.NET 2.0 applications

Introduction

Hibernate and Log4J are a de-facto standard of ORM (Object-relational mapping) and logging (respectively) in Java world. That's why both were ported to .NET runtime environment.

NHibernate is ORM solution and is intended for transparent binding .NET classes to database tables. NHibernate was designed to reduce time efforts to switch application to another database provider. It is achieved by means of replacing SQL with special NHibernate query language (HQL), providing dialects for several databases and providing special classes for retrieving/updating data in database.

Log4Net is logging framework which is useful for tracking errors in application and can write logs either to database table or flat files. You can download sample Visual Studio .NET project here

NHibernate in n-tier application

Speaking about modern approach to web-site development with ASP.NET you should be acquainted with n-tier approach. Generally n-tier application consists of data access tier, business logic tier, presentation tier and domain objects which spans across all tiers (this is 3-tier approach which is shown below)


Figure 1: A Typical 3-tier Model

In the figure above NHibernate manages Data Access tier which is responsible of loading/updating data into/from domain objects. Business Logic tier uses Data access objects to access data and perform computations upon data, while Presentation tier (in our case ASP.NET 2.0 pages) displays data to end-user and process user input passing it back to Business logic tier and so on.

Getting started with NHibernate

Now we can try to create a simple ASP.NET 2.0 application using NHibernate objects as Data Access tier. First of all we should start new ASP.NET project and add NHibernate binaries to project references (this is done via Add reference option from project menu. NHibernate binaries can be found at http://www.hibernate.org/6.html. We need several libraries from NHibernate package. The best way is to add all .dll files from NHibernate distribution located at bin\net-2.0 folder (in version 1.2). Among them you will notice log4net library which we will use later.

The next thing to do - is to configure our NHibernate to use certain database. This is done in web.config file for your web site. Here is example of typical content of web.config:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>
<system.web>
...
</system.web>
<appSettings>
...
</appSettings>
<nhibernate>
<add key="hibernate.show_sql" value="true"/>
<add key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect"/>
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
<add key="hibernate.connection.connection_string" value="Password=;Persist Security Info=True;User ID=sa;Initial Catalog=test;Data Source=127.0.0.1;"/>
<add key="hibernate.cache.use_query_cache" value="true"/>
</nhibernate>
</configuration>

The most important part for us in this fragment is connection string which should be set as a value of "hibernate.connection.connection_string" key. It should point to database intended for the test application.

Going on with session

As in ADO.NET we use database connection class to create queries or data adapter, in NHibernate we use session object which is responsible for maintaining connection to database, loading data into domain objects, detecting changes to domain object and syncing domain state with database. NHibernate requires at least one session to work. As NHibernate caches domain object instances to ensure that single row is loaded only once from database multi-user work with one session object is questionable. This issue differ usage of NHibernate in WinForms and ASP.NET application. The general idea is to have only one session per request and this can be achieved either by using singleton pattern on using HttpModule. We will use the last one. To do this we need to create a custom class, make it inherit IHttpModule interface and add following lines to web.config system.web section:

<httpModules>
<add type="Dummy.DAL.NHibernateHttpModule" name="NHibernateHttpModule"/>
</httpModules>

NHibernateHttpModule - the class you’ve created - is fair simple. The main idea is to open session upon beginning of the request and flush and close session when the request has ended. This class should contain following code:

public static readonly string KEY = "NHibernateSession";
private static ISession _session;

private void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
context.Items[KEY] = OpenSession();
}

private void context_EndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;

ISession session = context.Items[KEY] as ISession;
if (session != null)
{
try
{
session.Flush();
session.Close();
}
catch {}
}
context.Items[KEY] = null;
}

public static ISession CurrentSession
{
get
{
if (HttpContext.Current==null)
{
if (_session!=null)
{
return _session;
}
else
{
_session = OpenSession();
return _session;
}
}
else
{
HttpContext currentContext = HttpContext.Current;
ISession session = currentContext.Items[KEY] as ISession;
if (session == null) {
session = OpenSession();
currentContext.Items[KEY] = session;
}
return session;
}
}
}

The code above is rather simple. We open session once the request started (context_BeginRequest) and put it to the Context. When session ends we simply get session from Context and close it. OpenSession is the function responsible for creating NHibernate session instance from session factory object.

private static ISessionFactory factory = null;
private static ISessionFactory getFactory()
{
if(factory==null)
{
Configuration config;
config = new Configuration();
if (config==null)
{
throw new InvalidOperationException("NHibernate configuration is null.");
}
config.AddAssembly("Dummy.Assembly");
factory = config.BuildSessionFactory();
if (factory==null)
{
throw new InvalidOperationException("Call to Configuration.BuildSessionFactory() returned null.");
}
}
return factory;

}

public static ISession OpenSession()
{
ISession session;
session = getFactory().OpenSession();
if (session==null)
{
throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
}
return session;
}

In the code above we start by creating new Configuration object, which holds database connection settings, which in turn we specified in web.config file (do you remember hibernate.connection.connection_string key from web.config?). Once configuration is initialized we should tell NHibernate where to find domain objects for our application. It's good practice to have them in separate assembly along with Data Access layer and Business Logic layer. After SessionFactory is initialized we can use SessionFactory.OpenSession() method to retrieve new NHibernate session object.

Building domains

Our ASP.NET application is NHibernate ready, so our next step is to add some domains bound to database tables. First of all we need to create tables:

CREATE TABLE users (
UserID nvarchar(20) NOT NULL default '0',
Name nvarchar(40) default NULL,
Password nvarchar(20) default NULL,
PRIMARY KEY (UserID)
)

CREATE TABLE user_transactions (
TransID nvarchar(20) NOT NULL default '0',
UserId nvarchar(40) default NULL,
TransDate datetime default NULL,
Amount decimal default NULL,
PRIMARY KEY (TransID)
)

These are two simple tables where each user can have several roles. Next step is to create classes for domain objects. This process is pretty straightforward - we just need to duplicate table structure in the class and provide a way for linking two tables.

namespace Dummy.Assembly
{
public class User
{
private string userId;
private string name;
private string password;
private IList transactions;

public User()
{
}

public string Id
{
get { return userId; }
set { userId = value; }
}

public string Name
{
get { return name; }
set { name = value; }
}

public string Password
{
get { return password; }
set { password = value; }
}

public IList Transactions
{
get { return transactions; }
set { transactions = value; }
}
}
}

As transactions and users have many-to-one relationships we've created a property to hold a list of transactions belonging to this user. Transaction class is similar to User class but instead of IList it holds an instance of User object.

namespace Dummy.Assembly
{
public class Transaction
{
private string transId;
private User user;
private DateTime transDate;
private decimal amount;

public Transaction()
{
}

public string Id
{
get { return transId; }
set { transId = value; }
}

public User User
{
get { return user; }
set { user = value; }
}

public DateTime TransDate
{
get { return transDate; }
set { transDate = value; }
}

public decimal Amount
{
get { return amount; }
set { amount = value; }
}
}
}

Once domain classes are ready we should bind classes to tables and tell NHibernate which column matches which property from the domain class. This is done view hibernate mapping files - which must be placed in the namespace we've specified as a parameter for SessionFactory class constructor.

Important note: User.hbm.xml and Transaction.hbm.xml files should have "Embeded resource" type.

Mapping files shown above should have name in following format: <ClassName>.hbm.xml (.hbm - means Hibernate mapping). They are pretty simple: class tag tells hibernate to map Dummy.Assembly. User class form assembly named Dummy.Assembly to table users; id denotes primary key field (there must be primary key field in every domain). Property tags does all the magic with mapping properties (name attribute) of the class to respective column of table (column attribute). And more complex bag and many-to-one tags are used to specify relationship between two domains. Bag is used to map one-to-many relations by means of specifying foreign key (<key column="UserId" />) in table user_transactions and specifying target relation (one-to-many tag which tells NHibernate that foreign key is found in Transaction class). NHibernate while loading user from table will also take user primary key value and load data from Transaction table, making sure that property UserId is the same as user primary key value. Once all transactions are loaded they will be placed in respective collection - in our case it's Transaction in User class. Another important part there is lazy attribute of bag tag. Let's imagine we have 1 million users and each of them have 1 million transactions. When we want to load all users and reset their passwords at once we will force a big performance problem. That's because NHibernate will load us 1,000,000*1,000,000 records from the database, but we don't need transactions at the moment. Lazy loading is what will help us. Lazy loaded collection is not loaded once its parent object initialized, it only loads when our classes access this collection. Simply setting most of the relations to be lazy will boost performance a lot.

Once our mapping files are ready and placed in proper assembly along with domain classes we can go further. Now we need to use NHibernate session to manage our domain objects. The following code creates new user:

ITransaction transaction = session.BeginTransaction();
User u = new User();
u.Id = "admin";
u.Name = "Administrator";
u.Password = "secret";

session.Save(u);
transaction.Commit();

transaction = session.BeginTransaction();
u.Name = "Admin";
session.Update(u);
transaction.Commit();

Session variable is the NHibernate session we've instantiated in our HTTP module class. Example is fair simple. We simply create new domain object, fill its fields and tell the session to save changes. After that domain object will be persisted to database. In the next step we change user name and update the record in database. Now we need to load this user into our domain object:

User admin = (User) session.Load(typeof(User), "admin");

Load method loads user record identified by primary key value "admin" from database into our domain object. Isn't it simple?

Queries and Criteria

Loading objects by id good, but what to do when we need to load all objects or filter them by some criteria? Here comes HQL - a special query language used to retrieve data in NHibernate. It's as simple as SQL and moreover - it's SQL adapted for NHibernate. HQL instead of table columns uses properties from domain objects, instead of tables - domains. HQL is case sensitive, which means property names should be written in HQL just like in domain object. Those are the main differences. Examples of HQL below:

FROM User WHERE Name='Admin'
SELECT COUNT(*) FROM User u

In HQL you're allowed to drop SELECT statement. In this case all Users will be selected. To run query you should use simple query class provided with NHibernate:

IQuery query = session.CreateQuery("FROM User WHERE Name=:name");
query.SetString("name", "Admin");
IList users = query.List();

We simply create a query, then pass required parameters and execute List method which will return a collection of Users (even if there only one user). To retrieve only one object from query (as in example with count) you can use UniqueResult method of IQuery.

Another way to load your objects is to use ICriteria interface. Using criteria can be considered more OOP-oriented rather than using queries. Both have their advantages and drawbacks. Using criteria is as simple as using query:

ICriteria criteria = session.CreateCriteria(typeof(User));
criteria.Add(Expression.Eq("Name", "Admin"));
IList users = criteria.List();

We create criteria for user class, and then, using Expression class we tell the criteria to load users that have their "Name" field equal (Eq) "Admin".

It's seems easy to use NHibernate - but really it's not that easy. The main problem is that you can't test your query unless you run your code (SQL query you can run in special SQL editors/analyzers and NHibernate does not have one). Another problem is multiple updates of single instance of domain object is common for the beginners.

Adding Log4Net to ASP.NET application

To track possible problems Log4Net can be used. Its setup is easy. To make it work with ASP.NET you should add following lines to web.config file:

<configSections>
. . .
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>
<log4net>
<appender name="GeneralLog" type="log4net.Appender.RollingFileAppender">
<file value="Logs/general.txt"/>
<appendToFile value="true"/>
<maximumFileSize value="100KB"/>
<rollingStyle value="Size"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss} [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="GeneralLog"/>
</root>
<logger name="NHibernate" additivity="false">
<level value="DEBUG"/>
<appender-ref ref=" GeneralLog"/>
</logger>
</log4net>
. . .

Log4Net is based on appenders and loggers. Appenders are used to write logs to some destination (in our example - flat file), while loggers determine which appender should certain namespace use. There is always one appender and root logger (it logs from all classes). Additional loggers have a mandatory attribute name which specifies the namespace to which this logger is applied. For example - you can have a separate log files for Data Access layer and Business Layer. Logger level determines which of the log messages should be printed to log. There are several levels - DEBUG, INFO, WARN, ERROR, FATAL. Debug - include all levels, while Warn include Warn, Error and Fatal and so on.

The next thing is to add to our NHibernate HTTP module's context_BeginRequest procedure following lines to initialize log4net logging system:

log4net.Config.XmlConfigurator.Configure();

or

log4net.Config.BasicConfigurator.Configure();

Now we can use Log4Net:

private static ILog log = LogManager.GetLogger(typeof(User));

public void SomeMethod() {
log.Info("SomeMethod called");
try {
session.Load(typeof(User), "admin");
} catch (Exception ex) {
log.Error("Failed to load amin user");
log.Debug("Stack trace follows", ex);
} finally {
log.Info("SomeMethod end");
}
}

In example we create logger, giving it the typeof(User). This will be used by Log4Net to determine what logger and appender from web.config file to use. Next we generate some log data of different levels (Info, Error, Debug). As our root logger is set to Debug level we will see all the messages. When we don't want a mess in our logs, we can simply change level of appropriate logger to Error. Then no messages from log.Debug and log.Info methods will appear in our log. That's good while using NHibernate because it likes logging much and this decrease performance and log files will grow fast. So it's a good idea to lower log level while setting up live server. Also it's a good practice to have log output in all try-catch blocks.

Make Charts in ASP.NET 2.0

Introduction

Graphs and charts are important tools for the analysis of data. In this tutorial, we will look at how to represent data in terms of a bar chart and pie chart. To make this possible, the latest rendition of Microsoft's Graphics Device Interface (GDI+) was brought on the market.

Scenarios to draw bar and pie charts

In our bar and pie chart example, we will represent the data of a fictitious country having 4 towns; namely: Town A, Town B, Town C and Town D. Their population will be modeled in terms of graphs and charts. The figures are given below:

Town
Population
A 1,000,000
B 600,000
C 2,500,000
D 800,000


Bar chart that should be outputted


Pie chart that should be outputted

Useful classes

Below are some of the useful classes that will be used to achieve what we want:

The Bitmap class

In windows application, you would have output graphical contents to one of the windows. Whereas in ASP.NET, there are no window. Fortunately, there is a class that can act as the drawing surface and that is the Bitmap class.

As any drawing surface has a width and a height, so does the Bitmap object. So, when we will create a Bitmap object, we will pass in a width and a height as parameters in the constructors. Moreover, we have to specify “Pixel Format Argument” which means whether the pixel format should be 24-bits RGB color, 32-bits RGB color, GDI-colors etc.. In short the following code will do the work.

Dim Image As New Bitmap(500, 300, PixelFormat.Format32bppRgb)

The Graphics class



Once you have created the Bitmap object, you need to obtain a Graphics object that references that Bitmap object. The following code will do the job.

' Get the graphics context for the bitmap.
Dim
g As Graphics = Graphics.FromImage(Image)

The x vs. y coordinates system is illustrated in the figure below.


Coordinate axes for the Bitmap object

The Fun begins

Now that you have a “surface to draw”, as you may have guessed, you will need a pen or brush to draw. To create a pen, a few properties of the pen should be specified; like the color of the pen and the width of the pen...

' Create a pen for drawing
Dim redPen As New Pen(Color.Red, 10)

' Create a blue brush
Dim blueBrush As New SolidBrush(Color.Blue)

Useful methods for drawing

Now that we have a set of drawing tools at our disposal, the graphics class also has a set of interesting methods to simplify our work for drawing objects like: circles, rectangles, text and the list goes on.

For this tutorial, we will be using the following self explanatory methods.

Method Name

Purpose

Clear ( )

Cleans the graphics object

(Pretty much like cleaning the whiteboard)

DrawString ( )

For outputting text

FillPie ( )

Draw a pie slice (useful for pie chart)

FillRectangle ( )

Draws a rectangle (useful for bar charts)

DrawLine ( )

Draws a line

N.B. the above methods may be overloaded, so we used the names only. It is left to you as an option to explore how the overloaded methods differ.

Some concerns needed to draw bar charts and pie charts

  • As the y-axis is reversed, some further calculations need to be done to place the bar charts on the bitmap object.
  • We shall use one color representing each town.
  • The formula for calculating a sector in a pie chart for a population[i] is:
    (population[i] / totalPopulation) * 360

Coding the Chart Generator functions

First of all create 2 buttons and label them “Barchart” and “Piechart” as shown in the figure below:


Next, we shall declare some variables for the Bitmap object, Graphics object, population values, town names and the color representing each town. The code is as follows:

' Variables declaration
Private myImage As Bitmap
Private g As Graphics
Private p() As Integer = {1000000, 600000, 2500000, 80000}
Private towns() As String = {"A", "B", "C", "D"}

Private myBrushes(4) As Brush

Next, in our Page_Load event, we will call a function that will create objects from the Bitmap and Graphics classes. We should also create the brushes that will be used for drawing the charts. The code snippet below does the job:

' Create an in-memory bitmap where you will draw the image.
' The Bitmap is 300 pixels wide and 200 pixels high.
myImage = New Bitmap(500, 300, PixelFormat.Format32bppRgb)

' Get the graphics context for the bitmap.
g = Graphics.FromImage(myImage)

' Create the brushes for drawing
myBrushes(0) = New SolidBrush(Color.Red)
myBrushes(1) = New SolidBrush(Color.Blue)
myBrushes(2) = New SolidBrush(Color.Yellow)

myBrushes(3) = New SolidBrush(Color.Green)

The bar charts have to be placed in a manner that we could draw the axes and label them as well. So, we declare an interval variable that will space the bar charts.

' Variables declaration
Dim i As Integer
Dim xInterval As Integer = 100
Dim width As Integer = 90
Dim height As Integer
Dim blackBrush As New SolidBrush(Color.Black)

For i = 0 To p.Length - 1
height = (p(i) \ 10000) ' divide by 10000 to adjust barchart to height of Bitmap

' Draws the bar chart using specific colours
g.FillRectangle(myBrushes(i), xInterval * i + 50, 280 - height, width, height)

' label the barcharts
g.DrawString(towns(i), New Font("Verdana", 12, FontStyle.Bold), Brushes.Black, xInterval * i + 50 + (width / 3), 280 - height - 25)

' Draw the scale
g.DrawString(height, New Font("Verdana", 8, FontStyle.Bold), Brushes.Black, 0, 280 - height)

' Draw the axes
g.DrawLine(Pens.Brown, 40, 10, 40, 290) ' y-axis
g.DrawLine(Pens.Brown, 20, 280, 490, 280) ' x-axis
Next

The above code has only to be linked with the Click event of the “Barchart” button that was placed ok the page before.

We have to keep track of the total angle so far and add the current angle produced by population[i] and use the FillPie method to draw the piechart.

Next, to draw the pie chart, we make use of the following code:

' Variables declaration
Dim i As Integer
Dim total As Integer
Dim percentage As Double
Dim angleSoFar As Double = 0.0

' Caculates the total
For i = 0 To p.Length - 1
total += p(i)
Next

' Draws the pie chart
For i = 0 To p.Length - 1
percentage = p(i) / total * 360

g.FillPie(myBrushes(i), 25, 25, 250, 250, CInt(angleSoFar), CInt(percentage))

angleSoFar += percentage

' Draws the lengend
g.FillRectangle(myBrushes(i), 350, 25 + (i * 50), 25, 25)

' Label the towns
g.DrawString("Town " & towns(i), New Font("Verdana", 8, FontStyle.Bold), Brushes.Brown, 390, 25 + (i * 50) + 10)
Next

The above code has to be linked with the “Piechart” button’s Click event.

It is important to place all your codes in Try..Catch block as a good programming practice.

Now that we have the methods required to draw the charts, we should now output them on the browser. The piece of code below simply outputs the chart:

' Render the image to the HTML output stream.
myImage.Save(Response.OutputStream, _
System.Drawing.Imaging.ImageFormat.Jpeg)

Remember that myImage is the Bitmap object that we created earlier.

Et voila! Now, you can play around with the set of methods to produce other drawings or to improve the classes. You can also explore the rich set of methods and classes that the .NET framework provides us for producing drawings.

Note that every new feature will take time. If your projects requirements demands a lot of working hours, you can consider some more professional solution, like ChartingControl.NET. This control will give you different chart types including 3D charts. However, every GDI+ solution will return you a static chart image. If you want to REALLY impress your clients or users you must have Rich Chart Server for .NET. On server side you work with ASP.NET control like with every other control, but it produces beautiful and interactive Flash charts on client side. It provides animations, links, interaction with clients and even audio narration. Check examples on chart gallery to see what I am talking about, it is simply amazing!

Application Level Error Handling in ASP.NET

ASP.NET provides many different ways for error handling. Error handling starts on page level with try-catch blocks and Page_Error procedure. To find out more about this see Errors and Exceptions in ASP.NET tutorial. This tutorial goes one step further and explains error handling on Application level. This includes handling errors with Application_Error procedure in Global.asax or by using custom http module. These methods are usually used to log error details in text file, database or Windows EventLog, or to send notification e-mails to administrator.

Handling errors in Application_Error in Global.asax

Global.asax is optional file. Your site can work without it, but it could be very useful. You can have only one Global.asax in the root folder of your web site. Global.asax contains Application_Error procedure which executes whenever some unhandled error occurs. By using Application_Error, you can catch all unhandled errors produced by your web site, and then write a code to save error messages to database, text file or Windows EventLog, send notificationn e-mail, write some message to user, redirect user to other page with Response.Redirect etc. If you used Server.ClearError() in Page_Error procedure, Application_Error in Global.asax will not execute. Implementation code is similar to Page_Error code above:

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();
string ErrorDetails = CurrentException.ToString();433333

// Now do something useful, like write error log
// or redirect a user to other page
...
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()
Dim ErrorDetails As String = CurrentException.ToString()

' Now do something useful, like write error log
' or redirect a user to other page
...

End Sub

Writing errors to EventLog

One of the ways to track errors is to write them to EventLog. If your web site is on shared hosting you probably can't write to EventLog because of security issues. If you are on dedicated server, you need to enable "Full control" to EventLog for ASPNET account. You can do this in Registry Editor. Go to Start menu -> Run... and type regedt32 or regedit and press Enter. Registry Editor will show. Go to HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog\, right mouse click and select Permissions... from context menu, like in image bellow.

Registry editor
Registry editor

Click on Permissions... item to show a dialog. Give Full Control to your ASP.NET Machine Account, like in next image (include child objects too!).

Registry permissions dialog
Registry permissions dialog

Now ASPNET account has enough rights. To write in EventLog we can use Try...Catch syntax or Page_Error event, but to avoid duplication of code better option is to use Application_Error in Global.asax file. Code inside Application_Event procedure will be executed every time when some error occurs on web site.

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();

// Name of the log
string SiteLogName = "My Web Site Errors";

// Check if this log name already exists
if (EventLog.SourceExists(SiteLogName) == false)
{
EventLog.CreateEventSource(SiteLogName, SiteLogName);
}

// Write new error log
EventLog NewLog = new EventLog();
NewLog.Source = SiteLogName;
NewLog.WriteEntry(CurrentException.ToString(), EventLogEntryType.Error);
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()

' Name of the log
Dim SiteLogName As String = "My Web Site Errors"

' Check if this log name already exists
If EventLog.SourceExists(SiteLogName) = False Then
EventLog.CreateEventSource(SiteLogName, SiteLogName)
End If

' Write new error log
Dim NewLog As EventLog = New EventLog()
NewLog.Source = SiteLogName
NewLog.WriteEntry(CurrentException.ToString(), _
EventLogEntryType.Error)
End Sub

Reading web site error messages from EventLog

After we created procedure for writing site error messages to Windows EventLog, we need to read them too. You can read these messages by using Windows Event Viewer, located in Control Panel. Also, you can show these messages on web page in some kind of report. To read EventLog error messages with ASP.NET and show them on page you can use code like this:

[ C# ]

using System;

// We need these namespace to read EventLog
using System.Diagnostics;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Load errors to EventLog object
EventLog MySiteErrorLogs = new EventLog("My Web Site Errors");

foreach(EventLogEntry SiteErrorLog in MySiteErrorLogs.Entries)
{
// Find when error occured
Response.Write("Time generated: " +
SiteErrorLog.TimeGenerated.ToString() + "<br />");
// Show error details
Response.Write(SiteErrorLog.Message + "<hr />");
}
}
}

[ VB.NET ]

Imports System

' We need these namespace to read EventLog
Imports System.Diagnostics

Partial Class DefaultVB
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' Load errors to EventLog object
Dim MySiteErrorLogs As EventLog = New EventLog("My Web Site Errors")

For Each SiteErrorLog In MySiteErrorLogs.Entries
' Find when error occured
Response.Write("Time generated: " & _
SiteErrorLog.TimeGenerated.ToString() & "<br />")
' Show error details
Response.Write(SiteErrorLog.Message + "<hr />")
Next
End Sub
End Class

Logging of unhandled errors to text file or database

On the same way, we can use Application_Error in Global.asax to write error message to text file or in some table in database. In this example, Application_Error procedure contains a code that writes error information to .txt file.

[ C# ]

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException;
CurrentException = Server.GetLastError();

// Write error to text file
try
{
string LogFilePath = Server.MapPath("ErrorLog.txt");
StreamWriter sw = new StreamWriter(LogFilePath);
// Write error to text file
sw.WriteLine(CurrentException.ToString());
sw.Close();
}
catch (Exception ex)
{
// There could be a problem when writing to text file
}
}

[ VB.NET ]

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

' Get current exception
Dim CurrentException As Exception
CurrentException = Server.GetLastError()

' Write error to text file
Try

Dim LogFilePath As String = Server.MapPath("ErrorLog.txt")

Dim sw As System.IO.StreamWriter = _
New System.IO.StreamWriter(LogFilePath)
' Write error to text file
sw.WriteLine(CurrentException.ToString())
sw.Close()
Catch ex As Exception
' There could be a problem when writing to text file
End Try
End Sub

Better error logging with Log4Net

If you simply write error details to text file you will get simple, but not scalable solution. In case of large number of concurrent users, text file could be locked for writing and you will get another error. Log4Net is more scalable solution for this problem. To find out how to use Log4Net see Using NHibernate and Log4Net in ASP.NET 2.0 applications tutorial.

How to send e-mail with error details

You can place a procedure for sending an email inside Application_Error, try-catch block or any other error handling code. Function to send email is pretty simple. In this example, code for sending notification e-mails is located in Application_Error procedure in Global.asax file. On this way e-mail will be sent whenever some undhandled error occurs.

[ C# ]

<%@ Application Language="C#" %>

<%-- We need these namespaces to send e-mail --%>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Mail" %>

<script runat="server">

void Application_Error(object sender, EventArgs e)
{
// Get current exception
Exception CurrentException = Server.GetLastError();
string ErrorDetails = CurrentException.ToString();

// Send notification e-mail
MailMessage Email =
new MailMessage("admin@yoursite.com",
"admin@yoursite.com");
Email.IsBodyHtml = false;
Email.Subject = "WEB SITE ERROR";
Email.Body = ErrorDetails;
Email.Priority = MailPriority.High;
SmtpClient sc = new SmtpClient("localhost");
sc.Credentials =
new NetworkCredential("EMailAccount", "Password");
sc.Send(Email);
}

</script>

[ VB.NET ]

<%@ Application Language="VB" %>

<%-- We need these namespaces to send e-mail --%>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Mail" %>

<script runat="server">

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = Server.GetLastError()
Dim ErrorDetails As String = CurrentException.ToString()

' Send notification e-mail
Dim Email As MailMessage = _
New MailMessage("admin@yoursite.com", _
"admin@yoursite.com")
Email.IsBodyHtml = False
Email.Subject = "WEB SITE ERROR"
Email.Body = ErrorDetails
Email.Priority = MailPriority.High
Dim sc As SmtpClient = New SmtpClient("localhost")
sc.Credentials = _
New NetworkCredential("EMailAccount", "Password")
sc.Send(Email)
End Sub

</script>

Handling errors with custom HttpModule

Custom Http module advantage is that module doesn't require changes in your web application code. Http module will catch the same event like Application_Error procedure. To create custom Http Module, start new project in Visual Studio. Project type should be Class Library. Add code like this:

[ C# ]

using System;
using System.Web;

namespace CommonModules
{
public class ErrorHandlingModule : IHttpModule
{

public void Init(HttpApplication app)
{
app.Error += new EventHandler(this.HandleErrors);
}

public void HandleErrors(object o, EventArgs e)
{
// Get current exception
Exception CurrentException = ((HttpApplication)o).Server.GetLastError();

// Now do something with exception with code like in examples before,
// send e-mail to administrator,
// write to windows EventLog, database or text file
}

public void Dispose()
{
// We must have this procedure to implement IHttpModule interface
}
}
}


[ VB.NET ]

Imports System
Imports System.Web

Namespace CommonModules
Public Class ErrorHandlingModule
Implements IHttpModule

Public Sub Init(ByVal app As HttpApplication)
app.Error += New EventHandler(this.HandleErrors)
End Sub

Public Sub HandleErrors(ByVal o As Object, ByVal e As EventArgs)
' Get current exception
Dim CurrentException As Exception = (CType(o, _
HttpApplication)).Server.GetLastError()

' Now do something with exception with code like in examples before,
' send e-mail to administrator,
' write to windows EventLog, database or text file
End Sub

Public Sub Dispose()
' We must have this procedure to implement IHttpModule interface
End Sub
End Class
End Namespace

To use the module in web application, you need to add few lines in Web.Config file.

<httpModules>
<add name="ErrorHandlingModule" type="CommonModules.ErrorHandlingModule, CommonModules "/>
</httpModules>

More about how to create and implement custom Http modules in ASP.NET see in How To Create Your Own Http Module tutorial.

ASP.NET Error Handling Remarks

Be careful to handle possible errors in your Application_Error procedure. This procedure is executed when every error occurs, so Application_Error will call itself and it is possible to go in to infinite loop.

Writing error logs to text file can cause a problem with concurrency if a lot of errors need to be logged simultaneously. To make this task easier, you can use Log4Net. Also, there is pretty nice complete solution called Elmah now available at Google code.

How To Create Your Own Http Module

Introduction

The purpose this tutorial is to understand what is a Http module and how to create your own Http module. Through out this tutorial I will be using C# to explain some of the concepts.

Reader is assumed to know how to use of Visual Studio. Has good understanding of ASP.NET web application and last but not least know programming using .NET base class libraries framework.

What is an Http module?

So what is a Http module?

Simply put, Http module is a piece of code that sits between the client and the web server. All the request that are made through the web server passes through http module.

Lets have a look at very high level, how the request are process by ASP.NET framework. When a client makes a request to your web server the request comes in the form of Http Request. The Http Request flows through series of Http Modules. Each of the Http module have full control over the Http request. They process or modify the request. On the request passes through all the modules it is handed over to Http handler. Http handlers process the request and response passes through the all Http modules in reverse order.

ASP.NET framework already has few Http modules such as session state, authorization etc. You can find list of these modules in the machine.config file under httpModules section.

So what we can we do with Http module?

If we are creating our own Http module, we can respond to either ASP.NET events or user events in a way we want it to respond.

Authoring Http Module.

The following steps are followed while creating an Http module.

  1. Implement IHttpModule.
  2. Register the events of interest in Init method.
  3. Implement event handlers.
  4. Implement Dispose if required.
  5. Register the module in web.config.

Learn by example.

It is always easy to understand if it is explained with example. Let us follow the same practice. Let us say I have a requirement where I need to log the requests that are coming to my web application. Later on I will use this information to analyze my web site traffic.

Create RequestLoggerModule

  1. Open Visual Studio.
  2. Create New Class Library project and name it RequestLoggerExample.
  3. Delete the Class1.css that is created by default.
  4. Add a class by name RequestLoggerModule.
  5. Add reference to System.Web assembly.
  6. Import the name space System.Web.
  7. Now make RequestLoggerModule to implement IHttpModule.

At this stage RequestLoggerModule code should look like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace RequestLoggerExample
{
public class RequestLoggerModule:IHttpModule
{
#region IHttpModule Members
public void Dispose()
{

}

public void Init(HttpApplication context)
{

}


#endregion
}
}

The best place to log the request is the BeginRequest. In the Init method attach a event handler to BeginRequest event of the HttpApplication as shown below.

context.BeginRequest += new EventHandler(context_BeginRequest);

In the context_BeginRequest method trap the request stream and log it to a file as shown below.

void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
StreamWriter sw = new StreamWriter(@"C:\requestLog.txt");
StreamReader reader = new StreamReader(app.Request.InputStream);
sw.WriteLine(reader.ReadToEnd()); sw.Close();
}

Note that writing to a file will through exception if the user context under which ASP.NET is running do not have write access. It is not a good idea to give a write permission in your real application.

Now your code should look like below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO;

namespace RequestLoggerExample
{
public class RequestLoggerModule:IHttpModule
{
#region IHttpModule Members

public void Dispose()
{

}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}

void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
StreamWriter sw = new StreamWriter(@"C:\requestLog.txt");
StreamReader reader = new StreamReader(app.Request.InputStream);
sw.WriteLine(reader.ReadToEnd());
sw.Close();
}

#endregion
}
}

Configuring web application.

We have built the Http module successfully, let us see how we can make use of the module that we have created.

Follow the below steps to configure your existing ASP.NET we application to use the RequestLoggerModule.

  1. Copy RequestLoggerExample.dll to bin folder of your web application.
  2. Open the web.config file of you web application.
  3. Add the below shown section to your web.config under system.web node.

    <httpModules>
    <add name="RequestLoggerModule" type="RequestLoggerExample.RequestLoggerModule, RequestLoggerExample"/>
    </httpModules>

If you want this module to be used by all the application that are hosted on your server, you need to add the above section to machine.config file.