Evolving ASP.NET Apps - Dealing with Dependencies

This post is a continuation of the Evolving ASP.NET series:

Evolving ASP.NET Apps - Introduction
Evolving ASP.NET Apps – Evaluating the Code
Evolving ASP.NET Apps – Moving to a Web Application

In the second post in this series we took a close look at the source code for BugTracker.NET. We found a number of issues ranging from security problems to out of date third party libraries to the lack of proper directory structure.

In this post and the few that follow we will explore how to deal with references to older 3rd party packages. It is worth reviewing each of these packages to check for the following:

  • Is there a newer version of the package available? Older versions may no longer be supported, or in the case of some web frameworks they may not work in new browsers.
  • Is the package available on Nuget? Pulling from Nuget will save a lot of time for future updates.
  • Are there better options for the packages? It is possible that the package has been superseded by some newer and better tool.

.NET Packages

In BugTracker.NET, we have identified 3 different .NET packages that were being referenced: log4net, Lucene.NET and SharpMimeTools.

Log4net is a logging framework that provides a wrapper to various different logging mechanisms. It is a very commonly used framework that is not updated particularly frequently.

Lucene.NET is a full text search engine. You can insert records into it and then have Lucene return the matching documents. Lucene is actually is the foundational technology behind Elastic Search as well as Solr.

Finally the SharpMimeTools library provides the ability to read and decode MIME messages. This is the encoding used in e-mail attachments. In BugTracker.NET it is used to allow reading e-mails sent to open bugs.

All appear to be very out-of-date. We'll take a look at each one of these to see what options we might have to replace them.

JavaScript Libraries

Being a web application you can be fairly certain that there are some JavaScript libraries at work. Indeed we find that jQuery, a number of jQuery plugins and a rich text editor called CKEditor are all being used to some extent.

These packages are pretty much all available in nuget which is a far better option that manually downloading and including the packages. We'll try moving all of the third party JavaScript components to nuget and also reorganizing the code a little. The goal is to make the directory structure as similar as possible to what you would get creating a fresh project in Visual Studio. This will help out future developers.

In this post, we’ll limit ourselves to updating log4net to the latest. Correcting all dependencies is too long for a single post.

log4net

BugTracker is referencing v1.1.4322 of log4net. The current supported version of log4net is v1.2.13 and it is available on Nuget. Upon reviewing the release notes, we can see that there are a large number of bug fixes between v1.1.4322 and v1.2.13. Unfortunately, there are also a number of breaking changes in the v1.2 release. We will need to review how BugTracker is using log4net to assess the difficulty of upgrading to the latest.

To our surprise, BugTracker is referencing the log4net package but never actually uses the log4net. After doing a little digging, we see some custom logging code in Global.asax:

 string path = Util.get_log_file_path(); 

// open file 
StreamWriter w = File.AppendText(path); 

w.WriteLine("\nTIME: " + DateTime.Now.ToLongTimeString()); 
w.WriteLine("MSG: " + exc.Message); 
w.WriteLine("URL: " + Request.Url); 
w.WriteLine("EXCEPTION: " + exc); 
w.WriteLine(server_vars_string.ToString()); 
w.Close();
  

There are definitely problems with this code. There is one other method in util.cs that does some logging. This method is overly complex and definitely needs some fixing. Rather than try to fix these methods, we are going to replace the custom logging code with a standard .NET logging package.

Choosing a Framework

The 2 most common logging frameworks in .NET are log4net and NLog. Both are acceptable choices and we won't get into the details of comparing the 2 frameworks. We are going to move forward with NLog as it is a little newer and seems to have a more active community.

First, we need to remove the reference to the old log4net assembly and delete the file from the references folder. Now we can add a reference to the latest version of NLog using the Nuget Pacakge Manager Console (Tools -> Nuget Package Manager -> Package Manager Console).

 Install-Package NLog
  

Configuring NLog

Next, we need to configure NLog. This can be done using xml configuration files, which is very flexible but also a little messy. Since we are only looking for very basic file logging to replace the existing logging functionality, we would prefer to configure logging programatically in C#.

Let's start by creating a App_Start folder that will contain classes that are used to configure components of the application when it starts up. This is a pattern that is used in newer ASP.NET applications and can be seen when creating a new ASP.NET application in Visual Studio 2013.

In the startup folder, we will create a static LogConfig class that will contain a static Configure method with all our log4net configuration.

 public static class LoggingConfig 
    { 
        public static void Configure() 
        { 
            var config = new LoggingConfiguration(); 
            var fileTarget = new FileTarget(); 

            fileTarget.FileName = Path.Combine(Util.get_log_folder(), "btnet_log.txt"); 
            fileTarget.ArchiveNumbering = ArchiveNumberingMode.Date; 
            fileTarget.ArchiveEvery = FileArchivePeriod.Day; 
            config.AddTarget("File", fileTarget); 

            //Turn logging on/off based on the LogEnabled setting 
            var logLevel = Util.get_setting("LogEnabled", "1") == "1" ? LogLevel.Trace: LogLevel.Off; 
            config.LoggingRules.Add(new LoggingRule("*", logLevel, fileTarget)); 

            LogManager.Configuration = config; 
        } 
    }
  

In Global.asax.cs, call the logging configuration method in the Application_OnStart method:

 LoggingConfig.Configure(); 
  

Replacing Custom Logging Code

Now that NLog is configured, we can start replacing the old custom logging code.

Starting with the code in Global.asax.cs, replace the logging code with the new NLog logging code:

 Logger logger = LogManager.GetCurrentClassLogger(); 
logger.Fatal(exc);
  

Resolving Relative Paths

While testing this change, we appear to have encountered our first yak that requires some shaving.

The call to Util.get_log_folder() in LoggingConfig.Configure fails because of the weird way the application is attempting to resolve relative paths using some value that is cached in the HttpRuntime. Let's simplify this approach by using a more standard and fail-safe method of resolving relative paths.

View the commit >>

Now that the error logging is working as expected, let's replace the code in Util.write_to_log with the following simplified logging code:

 Logger log = LogManager.GetCurrentClassLogger(); 
log.Debug(s);
  

We can delete the method Util.get_log_file_path since it is no longer used.

View the commit >>

Email Notifications

There is a large block of code in Global.asax.cs that deals with sending out email notifications where unhandled exceptions occur. This logic is easily replaced using the Mail Target in NLog. By adding the following code to the LoggingConfig.Configure() method, we can delete all the custom email logic.

 var mailTarget = new MailTarget 
{ 
      UseSystemNetMailSettings = true, 
      To = Util.get_setting("ErrorEmailTo", ""), 
      From = Util.get_setting("ErrorEmailFrom", ""), 
      Subject = "BTNET Error Notification", 
      Layout = "${machinename}${newline} ${date} ${newline} ${message} ${newline}  ${exception} ${newline}" 
}; 
config.AddTarget("Mail", mailTarget); 

//Turn email notification on/off based on the LogEnabled setting 
var emailLogLevel = Util.get_setting("ErrorEmailEnabled", "1") == "1" ? LogLevel.Fatal : LogLevel.Off; 
config.LoggingRules.Add(new LoggingRule("*", emailLogLevel, mailTarget)); 
  

Again, we were able to reuse the existing settings in Web.config to configure NLog.

View the commit >>

We have now replaced all the custom logging code in BugTracker with a very popular and well supported open source logging framework.