Wednesday, January 28, 2015

The Common Service Locator

If you are requesting services directly from your IoC container, this should be a code smell that you should consider using the Common Service Locator instead for this. By using an IoC container, you have decoupled the execution of a task from the implementation, but by requesting these dependencies directly from the container, you are now coupling yourself to the container. If you decide that you want to change the container that you are using, you will have to update both the libraries and all the locations where you are directly requesting from the container. This is an additional headache that you can avoid by using the Common Service Locator.

The CSL acts a wrapper around your choice of IoC container and provides a common mechanism from which you can can request services from it. If you are to change the container that you are using, the way you request services would not be affected; all you will need to do is set the locator provider to use your new
container and everything else can remain the same. Here's a C# example using StructureMap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//Configure the container like before
var container = new StructureMap.Container(x =>
{
    x.For<IDataRepository<Car>>().Use<CarRepository>();
});

ServiceLocator.SetLocatorProvider(() =>
    new CommonServiceLocator.StructureMapAdapter.Unofficial.StructureMapServiceLocator(container));

//Get a service:
//var repo = StructureMap.ObjectFactory.GetInstance<IDataRepository<Car>>();
var repo = ServiceLocator.Current.GetInstance<IDataRepository<Car>>();

Where as previously the service would have been requested directly from the container (line 11), it is now being requested from the CSL. If you want to replace using StructureMap with something else, you would only have to makes changes to where you set the locator provider (line 8). In the example above, I am using the StructureMapAdapter that I installed via Nuget, but a quick Google shows that there are CSL adapters for all the major IoC containers.

Decoupling yourself from your IoC container is the next logical step in writing decoupled code and the Common Service Locator is a very easy way to achieve this.

Wednesday, January 14, 2015

The Beauty of Open Source

The beauty of Open Source is that you can look at the most up-to-date detailed version of the documentation: the source code itself. Whenever you want to know how to do something or how it works, you can take a look at the source code. Yes, this is not always advisable as the first course of action as it can be quite time consuming, especially if it is a large and complex project. However, when you do figure out what you are after by reviewing the source code, it is a much more rewarding process than finding the answers in the docs (plus you may learn a new coding practice / technique).

I wanted to use the Serilog.Extras.Web nuget package to enrich my log entries with the HttpRequestId for log message correlation. I noticed in addition to the HttpRequestId being added to each log message, there were additional log messages being created for "HTTP GET..." or "HTTP POST...". I did not really want these log messages being generated as they were filling the log viewer (Seq) with a lot of noise. A quick Google search lead me to this Github page and the following ApplicationLifecycleModule class:


// Copyright 2014 Serilog Contributors
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Linq;
using System.Web;
using Serilog.Events;

namespace Serilog.Extras.Web
{
    /// <summary>
    /// HTTP module that logs application request and error events.
    /// </summary>
    public class ApplicationLifecycleModule : IHttpModule
    {
        static volatile bool _logPostedFormData;
        static volatile bool _isEnabled = true;
        static volatile LogEventLevel _requestLoggingLevel = LogEventLevel.Information;

        /// <summary>
        /// Register the module with the application (called automatically;
        /// do not call this explicitly from your code).
        /// </summary>
        public static void Register()
        {
            HttpApplication.RegisterModule(typeof(ApplicationLifecycleModule));
        }

        /// <summary>
        /// When set to true, form data will be written via a debug-level event.
        /// The default is false. Requires that <see cref="IsEnabled"/> is also
        /// true (which it is, by default).
        /// </summary>
        public static bool DebugLogPostedFormData
        {
            get { return _logPostedFormData; }
            set { _logPostedFormData = value; }
        }

        /// <summary>
        /// When set to true, request details and errors will be logged. The default
        /// is true.
        /// </summary>
        public static bool IsEnabled
        {
            get { return _isEnabled; }
            set { _isEnabled = value; }
        }

        /// <summary>
        /// The level at which to log HTTP requests. The default is Information.
        /// </summary>
        public static LogEventLevel RequestLoggingLevel
        {
            get { return _requestLoggingLevel; }
            set { _requestLoggingLevel = value; }
        }

        /// <summary>
        /// Initializes a module and prepares it to handle requests.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application </param>
        public void Init(HttpApplication context)
        {
            context.LogRequest +=LogRequest;
            context.Error += Error;
        }

        static void LogRequest(object sender, EventArgs e)
        {
            if (!_isEnabled) return;

            var request = HttpContext.Current.Request;
            Log.Write(_requestLoggingLevel, "HTTP {Method} for {RawUrl}", request.HttpMethod, request.RawUrl);
            if (_logPostedFormData && Log.IsEnabled(LogEventLevel.Debug))
            {
                var form = request.Form;
                if (form.HasKeys())
                {
                    var formData = form.AllKeys.SelectMany(k => (form.GetValues(k) ?? new string[0]).Select(v => new { Name = k, Value = v }));
                    Log.Debug("Client provided {@FormData}", formData);
                }
            }
        }

        static void Error(object sender, EventArgs e)
        {
            if (!_isEnabled) return;

            var ex = ((HttpApplication)sender).Server.GetLastError();
            Log.Error(ex, "Error caught in global handler: {ExceptionMessage}", ex.Message);
        }

        /// <summary>
        /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
        /// </summary>
        public void Dispose()
        {
        }
    }
}

From this I could see the method generating the unwanted log message (LogRequest) checks _isEnabled before logging the "HTTP..." message. So, to stop these messages all you have to do is set _isEnable to false. The code clearly tells you how to do this too: set the static property IsEnable to false or ApplicationLifecycleModule.IsEnabled = false. Of course, I could have found this out from reading the docs but it would not have been half as much fun or as an insightful learning experience to what was going on inside this small part of Serilog.Extras.Web. 

What else did I learn from this exercise? I realised I could easily enrich the action method execution time telemetry logging I was already collecting with the requests raw URL using HttpContext.Current.Request.RawUrl to facilitate request URL correlation. I noticed that the Error method appears to be a global exception handler which could lead to this being a replacement for Elmah. I also saw the "volatile" C# keyword in use which prompted me to look into this further and discovered it's something not to be used lightly.

Maybe the next time you experience some behavior you were not expecting or want to know how to do something with an open source project, before rushing to the docs for an explanation, take a quick look at the source code and see what you can uncover first. You may just come away with a small sense of achievement and learn something new in the process.

Wednesday, January 7, 2015

Passing Property Names with C# Lambdas

How many times have you heard that using magic strings is a bad practice? Imagine you are passing a property name to a function that is going to use reflection to read its value. How do you ensure that the string is typo free and accurately represents the property? You could write a unit test to validate the name being passed, but there's nothing stopping you from copying and pasting the typo; you could create a static class and store the names as static, read-only properties; or maybe even retrieve it from another source that could be internal or external to the project. The problem with all these methods is that if there is any change to the property name itself, they also need to be updated.  

var book = new Book()
{
    Author = "George R. R. Martin",
    Title = "A Game Of Thrones",
    Publisher = "Bantam"
};

Console.WriteLine(ReadProperty(book, "Author"));

public object ReadProperty(object source, string propertyName)
{
    var propertyInfo = source.GetType().GetProperty(propertyName);

    return propertyInfo.GetValue(source);
}

If the property's name has been changed, this will not be detected until the function tries to retrieve the PropertyInfo for the property that has been renamed. As it no longer exists, this will result in a NullException being thrown. 

Miguel Castro introduces a simple solution for this in his Pluralsight course that ensures you are only using valid properties. This solution uses a lambda expression to validate that the property identified in the expression actually exists and if it doesn't, a compile error will result. By passing the name as a strongly typed property instead of as a string, you are rewarded with a safety net against typos and changes.

var book = new Book()
{
    Author = "George R. R. Martin",
    Title = "A Game Of Thrones",
    Publisher = "Bantam"
};

Console.WriteLine(ReadProperty(book, () => book.Author));

public object ReadProperty<T>(object source, Expression<Func<T>> propertyExpression)
{
    var memberExpression = propertyExpression.Body as MemberExpression;
    var propertyName = memberExpression.Member.Name;
    var propertyInfo = source.GetType().GetProperty(propertyName);

    return propertyInfo.GetValue(source);
}

The Expression wrapping the Func of T causes the compiler to "emit code to build an expression tree that represents the lambda expression." This provides the functionality to allow us to very easily interrogate the lambda expression and retrieve the name of the property that was identified.

If you are familiar with the ASP.NET MVC framework and HtmlHelpers, you should recognize the application of the above pattern as this is exactly what is being used to identify a property on the view model:


As the in type of the Func is set as the type of the view model, this limits the scope of the properties that can be identified to only those present on the view model; the out type (labeled as TProperty) still remains as the type of the property being identified.

That's it--now you no longer have to worry about passing property names as strings!