This is the second installment in the development of my home automation system using LightSpeed 3.0 and various other tools. I’ve got an overview here and of course you are encouraged to read Part 1 before diving in here.
I should mention before starting that I’ve changed a couple of projects in the solution. I’ve renamed the admin web application to: Gigabode.Admin.Web.UI. In addition, I’ve added a Gigabode.Admin.Web class library for classes that are specifically created to support the Admin Web UI.
I decided to start by setting up some stuff that I’m going to be using throughout the application, namely the logging and (Inversion of Control) IOC concerns. For the logging and exception handling, I’m going to be using the Enterprise Library. I’m going to go ahead and use StructureMap for the IOC container. I was going to use Unity since it’s already included with the Enterprise Library, but it doesn’t have a good per request lifetime manager, and although people have published a few, I’m not convinced they won’t lead to poor disposal of objects. StructureMap already has a per HttpContext built in. I’ve used Spring, Unity, and Windsor, but I’ve never used StructureMap. I have to say that it’s the most impressive IOC I’ve tried out so far. It’s very intuitive and full of features.
Logging Setup
In this project, I’m generally going to try to not start with the database. I’m going to try to model stuff in the LightSpeed designer and have it build my tables for me. Of course I’ll need to add scripts for indexes and such, but at least I can leverage LightSpeed to generate the DB tables. However, for the logging database, the Enterprise Library includes a script to set up the default logging tables and stored procedures, so I’ll use this to set up the database. There are a few things I don’t like about this script out of the box:
- The default script tries to set up a different database just for logging. For this project, I’m just going to add it to the existing database.
- The names of the tables are things like “Category”, much too general if they are going to exist in my overall database. So I’ve renamed them to all begin with “Logging”.
So I’ve updated the script to cover these changes. If you would like them for your own project, they can be downloaded here.
Setting Up Logging in the App
So now I’ve got the data structure in place, and I need to add in a way to actually log to the database. I usually set up three basic levels of logging: Error, Info, and Verbose. So to make this easier, I’m going to set up an interface to log to each of these levels in my domain project (ILogService), and then set up the implementation in the AppServices project (LogService).
Since I’m using the Enterprise Library (EL), I need to set up the configuration for this application block. Configuration is usually stored in the application config file (web.config in my case), but the EL includes an easy GUI tool for putting together the configuration so you don’t have to hack through all of the gnarly XML needed to set up the various properties. I’m not going into how to use this in detail, but I’ve pasted a shot of the config I’ve set up in the GUI. If you look under the Category Sources node, I’ve set up a category for Error, Information, and Verbose. I’ve also set up two listeners, one that writes to the database, and a backup that writes to a flat file source. The flat file listener is only used if a logging error or warning is produced by the EL logging block itself. Say for example it fails while logging to the database. There are several other listeners available, such as email and event log, but this will do for now.
Now that I’ve got a logging interface and an implementation, I need something to tie them together, so I need to go ahead and set up my IOC implementation. A lot of people like to wait a while before setting up IOC, but from my perspective, I know I’m going to be using it throughout the app, so I like to set it up right off the bat so that I can get my IOC pattern in place. I’m definitely in agreement with Chad Myers on this point.
1: using System;
2: using System.Diagnostics;
3:
4: namespace Gigabode.Domain.AppServices.Core
5: {
6: public interface ILogService
7: {
8: int LogVerbose(string message);
9:
10: int LogInfo(string message);
11:
12: int LogError(string message);
13:
14: int LogError(Exception ex);
15:
16: int LogMessage(string message, TraceEventType severity, int priority, params string[] categories);
17: }
18: }
ILogService Interface
1: using System;
2: using System.Diagnostics;
3: using Gigabode.Domain.AppServices.Core;
4: using Microsoft.Practices.EnterpriseLibrary.Logging;
5:
6: namespace Gigabode.AppServices.Core
7: {
8: /// <summary>
9: /// Logging utility class.
10: /// </summary>
11: public class LogService : ILogService
12: {
13: /// <summary>
14: /// Logging Categories
15: /// </summary>
16: public static class Category
17: {
18: public const string Error = "Error";
19: public const string Information = "Information";
20: public const string Verbose = "Verbose";
21: }
22:
23: public int LogVerbose(string message)
24: {
25: return LogMessage(message, TraceEventType.Verbose, 1, Category.Verbose);
26: }
27:
28: public int LogInfo(string message)
29: {
30: return LogMessage(message, TraceEventType.Information, 2, Category.Information);
31: }
32:
33: public int LogError(string message)
34: {
35: return LogMessage(message, TraceEventType.Error, 5, Category.Error);
36: }
37:
38: public int LogError(Exception ex)
39: {
40: if (ex != null)
41: {
42: return LogError(ex.ToString());
43: }
44: return 0;
45: }
46:
47: public int LogMessage(string message,
48: TraceEventType severity, int priority, params string[] categories)
49: {
50: var entry = new LogEntry
51: {
52: Message = message,
53: Priority = priority,
54: Severity = severity
55: };
56: foreach (var category in categories)
57: {
58: entry.Categories.Add(category);
59: }
60:
61: return LogEntry(entry);
62: }
63:
64: private static int LogEntry(LogEntry entry)
65: {
66: int eventID = 0;
67:
68: if (Logger.ShouldLog(entry))
69: {
70: eventID = GenerateEventID();
71: entry.EventId = eventID;
72: Logger.Write(entry);
73: }
74: return eventID;
75: }
76:
77: private static int GenerateEventID()
78: {
79: return int.Parse(DateTime.Now.ToString("yy").Substring(1)
80: + DateTime.Now.DayOfYear.ToString().PadLeft(3, '0')
81: + DateTime.Now.TimeOfDay.TotalMilliseconds.ToString().Substring(0, 5));
82: }
83:
84: }
85: }
LogService Implementation
Setting Up the IOC
I’m going to start by using the CommonServiceLocator(CSL). Even though I’m using StructureMap, I’d like to leave the door open in case I decide to switch it out for another IOC container at some point. CSL just sets up some common GetObject methods so that you don’t tightly couple to the IOC implementation. You still have to use the implementation of the container at the client application level to register the objects, so some work will be required if you decide to switch.
1: public interface IServiceLocator : IServiceProvider
2: {
3: object GetInstance(Type serviceType);
4: object GetInstance(Type serviceType, string key);
5: IEnumerable<object> GetAllInstances(Type serviceType);
6: TService GetInstance<TService>();
7: TService GetInstance<TService>(string key);
8: IEnumerable<TService> GetAllInstances<TService>();
9: }
Typically, like most people, I set up a thirdParty (or lib) folder at the same level as my source code folder. I put all my third party dll’s here, even if they are in the GAC. This just makes it easier to keep track of them and deploy them. So I’m dropping the CSL dll here as well as the StructureMap adapter implementation for the CSL.
Now, I’m going to go ahead and set up an object registrar class in the web application. This is why I went ahead and added the Gigabode.Admin.Web class library. This registration will be particular to the Admin web application, but I don’t want to stick this in the Global.asax because it’s just kind of messy and it’s hard to unit test.
Structure map follows a great pattern whereby you can set up “Registry” classes that perform IOC registration for a given application area. I really like this approach. Then when you create your container, you initialize it with the registry classes to set up your dependency associations. In order to facilitate this and tie it to the CommonServiceLocator, I made a little Registrar class to perform basic startup operations. This registrar is then initialized in the web application’s Global.asax.
1: using Microsoft.Practices.ServiceLocation;
2: using StructureMap;
3: using StructureMap.ServiceLocatorAdapter;
4:
5: namespace Gigabode.Admin.Web.Ioc
6: {
7: public class Registrar
8: {
9: public Container Container{ get; protected set; }
10:
11: public void Initialize()
12: {
13: Container = new Container();
14: Container.Configure(x =>
15: {
16: x.AddRegistry<AppServicesRegistry>();
17: });
18: ServiceLocator.SetLocatorProvider(() => new StructureMapServiceLocator(Container));
19: }
20: }
21: }
Registrar Class
1: public class Global : HttpApplication
2: {
3: private Registrar registrar;
4:
5: protected void Application_Start(object sender, EventArgs e)
6: {
7: registrar = new Registrar();
8: registrar.Initialize();
9: }
10: }
Global.asax
Setting Up the First Test
Now the everything is in place, I want to set up a unit test to make sure that my IOC system with the combination of StructureMap and the Common Service Locator actually works. Just a simple first unit test to verify that after the Registrar is initialized, the Common Service Locator returns my concrete LogService when I ask for an ILogService.
1: using SoftwareApproach.TestingExtensions;
2:
3: namespace Gigabode.Test.UnitTest.Web.Ioc
4: {
5: [TestClass]
6: public class RegistrarFixture:BaseUnitTest
7: {
8: [TestMethod]
9: public void LogService_Is_Registered()
10: {
11: var registrar = new Registrar();
12: registrar.Initialize();
13:
14: var logService = ServiceLocator.Current.GetInstance<ILogService>();
15: logService.ShouldNotBeNull();
16: }
17: }
18: }
First Test
I’m using MSTest as my framework, and it’s little different on the setup than NUnit. You can access various resources made available to the test framework through a TestContext object in the unit test. So the first thing I do is make a base unit test class to encapsulate this and derive my test classes from it. I’ll expand this as needed in the future. You may also notice in the test above that I’m not using an “Assert” statement. I found a library of extension methods that I really like that add a more readable interface for MSTest.
1: namespace Gigabode.Test.UnitTest
2: {
3: /// <summary>
4: /// Summary description for BaseUnitTest
5: /// </summary>
6: public abstract class BaseUnitTest
7: {
8: protected BaseUnitTest(){}
9:
10: /// <summary>
11: ///Gets or sets the test context which provides
12: ///information about and functionality for the current test run.
13: ///</summary>
14: public TestContext TestContext { get; set; }
15: }
16: }
Base Unit Test
So at some point, I’m sure you’ve probably been saying….”Enough with the setup! Go do something already!”. I’ll start the next installment with the actual use of LightSpeed 3.0 to pull the log entries and populate them into a data grid which will also allow a detail view of the information.