Pulling Things Together

Finally, it’s time to flesh out the method stubs that I created in Part 1, so that the web services are called. I started by adding a Web Reference in the MyCompany.ElmahExt project to the LoggingService that I created in Part 2.

First, I made the following additions to the ServiceErrorLog class.

namespace Beacon.ElmahExt
{
    using System;
    using System.Collections;
    using System.Linq; // Added...
    using System.Xml.Linq; // Added...
    using Elmah;
 
    public class ServiceErrorLog : ErrorLog
    {
        private LoggingService.Logging loggingServiceProxy = new LoggingService.Logging();
 
        // rows omitted for brevity...
    }
}

Log Method

The Log method using Elmah’s ErrorXml.EncodeString method to get the right structure for the error detail. The result returned by the LogError web method, is parsed and the id of the new error is returned.

public override string Log(Error error)
{
    if (error == null)
        throw new ArgumentNullException("error");
 
    string errorXml = ErrorXml.EncodeString(error);
 
    string result = loggingServiceProxy.LogError(
        this.ApplicationName,
        error.HostName,
        error.Type,
        error.Source,
        error.Message,
        error.User,
        error.StatusCode,
        error.Time.ToUniversalTime(),
        errorXml);
 
    XDocument doc = XDocument.Parse(result);
    XElement root = doc.Element("LogErrorResult");
    return root.Attribute("id").Value;
}

GetErrors Method

Parsing the error list:

public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
{
    string result = loggingServiceProxy.GetErrors(this.ApplicationName, pageIndex, pageSize);
    XDocument doc = XDocument.Parse(result);
    XElement root = doc.Element("GetErrorsResult");
 
    if (root != null)
    {
        var errors = from XElement el in root.Elements("Error")
                        select new
                            {
                                value = el.Value,
                                element = XElement.Parse(el.Value)
                            };
 
        foreach (var err in errors)
        {
            string errorId = err.element.Attribute("errorId").Value;
            Error error = ErrorXml.DecodeString(err.value);
            errorEntryList.Add(new ErrorLogEntry(this, errorId, error));
        }
 
        int total = int.Parse(root.Attribute("total").Value);
        return total;
    }
    else
        throw new InvalidOperationException("Unable to parse GetErrorsResult");
}

GetError

Returns detail for the specified error, or null if the error does not exist.

public override ErrorLogEntry GetError(string id)
{
    string result = loggingServiceProxy.GetError(this.ApplicationName, id);
    XDocument doc = XDocument.Parse(result);
    XElement root = doc.Element("GetErrorResult");
 
    if (root != null)
    {
        XElement errorEl = root.Element("Error");
 
        if (errorEl != null)
        {
            Error error = ErrorXml.DecodeString(errorEl.Value);
            return new ErrorLogEntry(this, id, error);
        }
        else
            return null;
    }
    else
        throw new InvalidOperationException("Unable to parse GetErrorResult");
}

Configuration

Finally, I need to hook up any configuration settings by modifying the ServiceErrorLog constructor. The original SqlErrorLog was set up to look for a connection string and an application name. As I have moved database access to the web service this just leaves the application name setting.

public ServiceErrorLog(IDictionary config)
{
    string webServiceUrl = config["webServiceUrl"] as string;
 
    if (!string.IsNullOrEmpty(webServiceUrl))
        loggingServiceProxy.Url = webServiceUrl;
 
    this.ApplicationName = config["applicationName"] as string ?? "";
}

UPDATED: After thinking about it, I have also added a webServiceUrl config item, which can be configured directly in the Elmah section of the web.config (previously I had a separate app.config file for the Extension library). This is a more logical place for this option, and will simplify use.

Final Thoughts

I now have support for multiple web applications running in web farm configuration and a single shared repository that sits in the correct layer of my architecture.

As a plus, I’m now free to re-factor the storage mechanism independently of the functional interface. Did I mention that I also have fairly strict guidelines for database design and security?

I need to make sure that I have turned off remote access using ELMAH’s security settings. In production I’ll need to log onto the server in order to check for errors. Eventually, I’ll probably have to implement a centralised reporting console.

I also need to start thinking about an email policy. Users didn’t like getting swamped with emails before. I still want to log every error, but only email important ones. This is a job for another day, but the obvious place to to this is in the new centralised service layer.