ASP.NET
Extend the GridView
The default GridView class has by default a lot of great features. However, some important features are missing. One of the most important is the ability to display the grid even if it is empty.
Here is a great class extending the GridView with some additional features.
Web application absolute path
Just a reminder on how to get the absolute path for the webapplication in ASP.NET
HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute(HttpContext.Current.Request.ApplicationPath)
This will return http://subdomain.domain.com/applicationname
You can drop those AppSettings with ApplicationPath
Using log4net in Web Applications – a real-life example
I have seen many different configurations for Log4Net. Log4Net is a very simple, but flexible framework and there are lot of ways to configure it. Here is a real-life example on how we are using in the applications I am working on.
First off, I won't say that this is the one and only way you should use Log4Net, but it handles most of the scenarios I can think of. Some people think that one should not write wrappers for the loggers and use the logging framework directly in the class, but to be honest I like the wrapper because it makes it very simple to use the logging framework without too much knowledge on how it works. I would say that 95% of what is being logged, is typically errors and debug/useful information. I can't see any problem using a wrapper as I get the information I need from the loggers.
Goals with this log configuration
- Most important is of course logging errors. All errors should be logged.
- Errors should be logged in a global file on the server that will role once a day.
- Errors should be sent by email.
- It should be possible to notify important information by email
- It should be easy for the developers to use the logging framework
- It should be possible to change log-level without restarting the application
Configuration and setup
Log4Net consists of only one DLL. Get the latest version and put it in your bin folder. Make a reference to it in the Web Application Project.
Log4net.config
Create a new config file in the root of your Web Application. Name it Log4Net.config and paste the following code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<threshold value="INFO" />
<file value="Log\[applicationname].log" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="'.'yyyyMMdd'.log'" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger %X{user} %X{url} - %message%newline" />
</layout>
</appender>
<appender name="SmtpAppenderError" type="log4net.Appender.SmtpAppender">
<!--Set threshold for this appender-->
<threshold value="ERROR" />
<to value="[someone]@[somewhere.com]" />
<from value="[someone]@[somewhere.com]" />
<subject value="Error from [applicationname]" />
<smtpHost value="[100.100.100.100]" />
<bufferSize value="1" />
<lossy value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger %X{user} %X{url} - %message%newline" />
</layout>
</appender>
<appender name="SmtpAppenderNotify" type="log4net.Appender.SmtpAppender">
<!--Set threshold for this appender-->
<threshold value="INFO" />
<to value="[someone]@[somewhere.com]" />
<from value="[someone]@[somewhere.com]" />
<subject value="Error from [applicationname]" />
<smtpHost value="[100.100.100.100]" />
<bufferSize value="1" />
<lossy value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<root>
<level value="Error" />
</root>
<logger name="Application">
<!--Set level for this logger-->
<level value="INFO" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="SmtpAppenderError" />
</logger>
<logger name="Notify">
<!--Set level for this logger-->
<level value="INFO" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="SmtpAppenderNotify" />
</logger>
</log4net>
</configuration>
Some comments on the Log4net.config file:
- The RollingFileAppender is great for rolling the logfile once a day.
- It has a threshold of INFO, meaning it will log anything from the application except debug information
- The logfiles are created in a subdirectory of web root. This should be changed on the production server to not expose information about the application.
- The datePattern is specifying how the rolling files are named. The current day will be named "applicationname.log" and yesterdays file will be named "applicationname.log.20081211.log". The rolling file extension (.log) makes it easy to open the file in the same editor as the current log. All files will be sorted correctly when using the ISO style date in the filename.
- In the conversionPattern you will see two speacial entries with the following format: %X{name}. These are custom formats which can be used for special purposes. This configuration adds the authenticated username and the url which fails if there is an error. This is useful information.
- The SmtpAppenders are used for sending emails. It's really not my favourite configuration, spamming the developers or some other people with emails when some errors occur, but some like it.
- The threshold is different for the two SmtpAppenders, as is the subject of the email.
- The buffersize is set to 1 in the example, meaning that the email is sent right away when an error occur. Increasing the buffer will hold the email until the buffer is reached. This can be useful on the production server, especially for the Notify appender as it is probably not that important to notify right away.
- Lossy is set to false, also to send the email right away. It is possible to hold the email until an evaluator is triggered, for instance when a error occurs. Setting lossy to true requires an evaluator.
- It is possible to add filters to the appenders to determine which emails should be sent and which to drop.
- There are two specific loggers that the application will use. The root logger can be used to log 3.party libraries also using Log4Net. I like to use specific loggers in the application and enabling the root logger if I am tracing an error where I need more information from 3.party libraries, ie. NHibernate or other frameworks using Log4net. Remember to set the level for the logger. If not set, the default level is WARN, which means INFO messages will not be logged.
To be able to use the Log4Net.config file instead of putting everything into web.config, you will need to add the following line to AssemblyInfo.vb:
<Assembly: log4net.Config.XmlConfigurator(ConfigFile:="Log4Net.config", Watch:=True)>
The advantage of separating the Log4Net configuration is that it is possible to change the configuration of the logging without restarting the application by changing web.config. It also gives an better overview of the configuration, not having to browse through web.config to find the logging configuration.
Logging unhandled errors
All unhandled errors should be logged, which is pretty easy to achieve in web applications. Here is the Global.asax file:
Imports System.Web
Imports log4net
Imports MainLib
Public Class [Global]
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the application is started
' Initialize the logger in this context.
LogManager.GetLogger(Me.GetType)
LogHandler.LogInfo("============================", LogHandler.LogType.General)
LogHandler.LogInfo(" Starting application", LogHandler.LogType.General)
LogHandler.LogInfo("============================", LogHandler.LogType.General)
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the session is started
End Sub
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' Fires at the beginning of each request
End Sub
Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
' Fires upon attempting to authenticate the use
End Sub
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Fires when an error occurs
LogHandler.LogError("Unhandled exception occured!",
HttpContext.Current.User, HttpContext.Current.Request.Url,
HttpContext.Current.Server.GetLastError())
End Sub
Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the session ends
End Sub
Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the application ends
End Sub
End Class
Comments on Global.asax
- I have added some information logging in the Application_Start event. This will trigger every time the applications starts or recycles. If this occurs often you probably have some problems with resources on the server, ie. too little memory available for the application.
- Application_Error occurs every time an unhandled error occurs. Use this to log all errors not logged elsewhere.
- I have to initialize the logging framework in Application_Start by calling
LogManager.GetLogger(Me.GetType). I'm not sure whether this is a bug or by design. It is not possible to let the LogHandler do the initialization. LogHandler is a wrapper class located in another assembly, maybe that is a problem, without really knowing why. LogManager is a class in Log4Net.
The LogHandler class
Imports System.Security.Principal
Imports log4net
Public Class LogHandler
Const _defaultApplicationLogger As String = "Application"
Public Enum LogType
General
Notify
End Enum
Public Shared Sub LogError(ByVal message As String, ByVal [error] As Exception)
Dim logger As ILog = LogManager.GetLogger(_defaultApplicationLogger)
If Not [error].InnerException Is Nothing Then
[error] = [error].InnerException
End If
If logger.IsErrorEnabled Then
logger.Error(message, [error])
End If
End Sub
Public Shared Sub LogError(ByVal message As String, _
ByVal user As IPrincipal, ByVal url As Uri, _
ByVal [error] As Exception)
SetOptionalParametersOnLogger(user, url)
LogError(message, [error])
End Sub
Public Shared Sub LogInfo(ByVal message As String, ByVal type As LogType)
Dim logger As ILog = Nothing
If type = LogType.Notify Then
logger = LogManager.GetLogger(LogType.Notify.ToString)
Else
logger = LogManager.GetLogger(_defaultApplicationLogger)
End If
If logger.IsInfoEnabled Then
logger.Info(message)
End If
End Sub
Public Shared Sub LogWarning(ByVal message As String, ByVal [error] As Exception)
Dim logger As ILog = LogManager.GetLogger(_defaultApplicationLogger)
If Not [error].InnerException Is Nothing Then
[error] = [error].InnerException
End If
If logger.IsWarnEnabled Then
logger.Warn(message, [error])
End If
End Sub
Public Shared Sub LogWarning(ByVal message As String, _
ByVal user As IPrincipal, ByVal url As Uri, _
ByVal [error] As Exception)
SetOptionalParametersOnLogger(user, url)
LogWarning(message, [error])
End Sub
Private Shared Sub SetOptionalParametersOnLogger(ByVal user As IPrincipal, ByVal url As Uri)
'set user to log4net context, so we can use %X{user} in the appenders
If Not user Is Nothing AndAlso user.Identity.IsAuthenticated Then
MDC.[Set]("user", user.Identity.Name)
End If
'set url to log4net context, so we can use %X{url} in the appenders
MDC.[Set]("url", url.ToString())
End Sub
End Class
Comments on LogHandler
- It's a pretty simple class which is easy to use. I could have added more wrapper methods with overload
- The LogType enum defines whether the message should be notified (emailed) or not.
- If I forget to set the level on the logger in the Log4Net.config, the logger.IsInfoEnabled will return false.
- I am using the MDC class in the Log4Net framework to add the custom entries in the logged message for user and url (see Log4Net.config above)
Handling an error and notifying with success
Sometimes when you know what could go wrong and you want to display a nice error message to the user, you could handle the error and display an errormessage to the user.
Example:
Private Function CreateUser() As User
Dim userName As String = TextBoxUserName.Text
Dim password As String = TextBoxPassword.Text
Try
Dim user As New User(userName, password)
LogHandler.LogInfo("Yahoo! User with username " & userName & " created.", LogHandler.LogType.Notify)
Return user
Catch iunex As InvalidUserNameException
PageTools.DisplayError(Page, iunex)
LogHandler.LogWarning("Error creating user with username " & userName, iunex)
Catch ipex As InvalidPasswordException
PageTools.DisplayError(Page, ipex)
LogHandler.LogWarning("Error creating user with password " & password, ipex)
End Try Return Nothing
End Function
Comments on CreateUser:
- If successful, a notification is sent by email by using the LogInfo method and using LogHandler.Logtype.Notify
- The User class will throw InvalidUserNameException or InvalidPasswordException in the credentials are invalid. I wish to display the errors to the user instead of displaying a general error page. In real life I would probably use some validation before calling the User constructor, but this is only an example.
- In addition to displaying the error to the user, the error is logged as a warning.This means it will be logged, but no email will be sent to the mailbox as this is not an error in the application.
- All other exceptions raised when creating the user will be caught in Application_Error in Global.asax and logged there.
Loginformation
Ok, now I have all the code I need to do decent logging. So what do I expect to see in the logfiles?
When building the application in DEBUG mode I will see the following in the logfile:
2008-12-11 16:56:24,453 [14] INFO Application (null) (null) - ============================
2008-12-11 16:56:24,468 [14] INFO Application (null) (null) - Starting application
2008-12-11 16:56:24,468 [14] INFO Application (null) (null) - ============================
2008-12-11 16:56:25,937 [14] WARN Application 3DX5G3J\knuth http://localhost/fdb/default.aspx -
Error creating user with username Knut Hamang
DAL.InvalidUserNameException: Username is already in use! Please select another username.
at DAL.User..ctor(String username, String password) in C:\Knut\code\Repository\Internal_Systems\fdb\trunk\DAL\User.vb:line 97
at fdb.default.CreateUser() in C:\Knut\code\Repository\Internal_Systems\fdb\trunk\Web\default.aspx.vb:line 69
Building in RELEASE mode I get the following information:
2008-12-11 16:59:21,406 [14] INFO Application (null) (null) - ============================
2008-12-11 16:59:21,421 [14] INFO Application (null) (null) - Starting application
2008-12-11 16:59:21,421 [14] INFO Application (null) (null) - ============================
2008-12-11 16:59:22,812 [14] WARN Application 3DX5G3J\knuth http://localhost/fdb/default.aspx -
Error creating user with username Knut Hamang
DAL.InvalidUserNameException: Username is already in use! Please select another username.
at DAL.User..ctor(String username, String password)
at fdb.default.CreateUser()
The difference is the stacktrace. I get the stacktrace in both cases, but I also get the line numbers in DEBUG mode. I can still read the trace and locate the method in the file that throws the error. The conclusion is that I get enough information to trace and fix the error, if there is one.
Please leave any comments on the configuration, and feel free to discuss different logging strategies for Web Applications.
A great article on understanding ViewState
This is as important to ASP.NET developers as it is to developers creating server controls (both user controls and custom controls). ViewState is difficult to understand, but when used correctly, it's very powerful.
Read and learn: http://infinitiesloop.blogspot.com/2006/03/truly-understanding-viewstate.html
Override the rendering of the ASP.NET 2.0 controls.
In April, the ASP.NET team will ship examples on how to use ControlAdapters for the ASP.NET 2.0 controls. This is a really good idea, because very few people knows how to override the rendering of the standard ASP.NET 2.0 controls. The ControlAdapters allows the developers to render the controls in a really nice way instead of using tables, which seems to be the Microsoft way. For instance, the menu control can be rendered with <ul><li> tags and be customized with CSS classes. This is a really nice feature that few people know about.
Search
Knut Hamang
Recent Posts
Recent Comments
- Yoteadviertoportero.Wordpress.Com on Using log4net in Web Applications – a real-life example
- Raj on Using log4net in Web Applications – a real-life example
- onitsuka tiger mexico 66 black/mazarine on Working with Visual Studio 2005 and ADO.NET
- reviews on Working with Visual Studio 2005 and ADO.NET
- RamS on Working with Visual Studio 2005 and ADO.NET