Posts tagged with: extension methods

Why extension methods should not be used as part of a framework.

Two of my favorite frameworks/libraries uses extension methods heavily. Autofac uses extension methods during registration and ASP.NET MVC3 for their HTML Helpers. Since extension methods are static, they are closed for extension. Which are one of the most important principles for object oriented programming.

For MVC I would like to be able to add a tooltip automatically for all editors and labels. It’s really simple, I just want to add a “title” attribute to all generated HTML elements where the ModelMetadata contains a “Description”. The source code for LabelFor looks like this:

    public static class LabelExtensions {
        public static MvcHtmlString Label(this HtmlHelper html, string expression) {
            return LabelHelper(html,
                               ModelMetadata.FromStringExpression(expression, html.ViewData),
                               expression);
        }

        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
            return LabelHelper(html, 
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData), 
                               ExpressionHelper.GetExpressionText(expression));
        }

        public static MvcHtmlString LabelForModel(this HtmlHelper html) {
            return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty);
        }

        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName) {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return tag.ToMvcHtmlString(TagRenderMode.Normal);
        }
    }

Hence it’s impossible to do anything about the helper. I do understand why they use extension methods. It’s great since it’s very easy to find all available helpers. The problem is that you can’t do anything do modify the data that they generate.

The solution

Convert all extension methods to facades for the real implementation, and use DependencyResolver in the extension methods to find the proper classes. Create some interfaces and default implementations for all generators.

The new LabelHelper:

internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName)
{

    var labelGenerator = DependencyResolver.Resolve<ILabelGenerator>();
    return labelGenerator.Create(html, metadata, htmlFieldName);
}

The default implementation (simplified example):

	public class LabelGenerator : ILabelGenerator
	{
	
		public virtual void Create(HtmlHelper html, ModelMetadata metadata, string htmlFieldName)
		{
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
			ProcessTag(tag);
            return tag.ToMvcHtmlString(TagRenderMode.Normal);
		}
			
		protected virtual void ProcessTag(TagBuilder tag, ModelMetaData metaData)
		{
		}
	}

Which would allow me to create the following implementation:

	public class LabelWithTitlesGenerator : LabelGenerator
	{
		protected override void ProcessTag(TagBuilder tag, ModelMetaData metaData)
		{
			if (metaData.Description != null)
				tag.Attributes.Add("title", metaData.Description);
		}
	}

The only thing left is to inject it in my container and create a small jquery script which creates the actual tooltip using the “title” attribute.

Summary

Extension methods are closed for extension. Once declared you can not do anything to extend the functionality that they provide. It’s the same as if you would develop a framework where you put sealed on all your classes.

Extension methods were created to be able to make life easier by proving convenience methods for existing classes. The intention was not that you should use them as a part of your framework since you close down that part for extension if you do. The extension methods become locking out methods ;O


More natural string formatting using an extension method

If you are like me, you don’t determine if you should include parameters in string before you start writing, thus you haven’t decided that string.Format should be used until it’s too late. You then need to go back and refactor that string, which can destroy the smooth coding flow that you got going.

Lets be saved by an extension method:

public static class StringExtensions
{
	public static string FormatWith(this string instance, params object[] arguments)
	{
	    return string.Format(instance, arguments);
	}
}

Which allows you to reformat the code as:

// Simple example
Console.WriteLine("Hello '{0}'!".FormatWith("world"));

// Multiple parameters:
var str = @"Happy birthday {0}!
    {1} is a beatiful age, isn't ?".FormatWith(user.Name, user.Age);

Extension methods to simplify database handling.

Two extension methods used to simplify database handling.

    public static class DbExtensionMethods
    {
        /// <summary>
        /// Create a new database command.
        /// </summary>
        /// <param name="connection">Connection used as factory.</param>
        /// <param name="commandText">SQL query.</param>
        /// <returns>Created command</returns>
        public static IDbCommand CreateCommand(this IDbConnection connection, string commandText)
        {
            var cmd = connection.CreateCommand();
            cmd.CommandText = commandText;
            return cmd;
        }

        /// <summary>
        /// Add a new parameter to the command
        /// </summary>
        /// <param name="cmd">Command</param>
        /// <param name="name">Parameter name, without prefix such as '@'.</param>
        /// <param name="value">Parameter value, datatype should be the same as in the database column.</param>
        public static void AddParameter(this IDbCommand cmd, string name, object value)
        {
            var parameter = cmd.CreateParameter();
            parameter.ParameterName = name;
            parameter.Value = value;
            cmd.Parameters.Add(parameter);
        }
    }

Usage

The following code:

            var cmd =
                new SQLiteCommand("UPDATE todo_items set title=@name, description=@description WHERE id = @id", _db);
            cmd.Parameters.Add(new SQLiteParameter("name", item.Title));
            cmd.Parameters.Add(new SQLiteParameter("description", item.Description));
            cmd.Parameters.Add(new SQLiteParameter("id", item.Id));

becomes

            var cmd = _db.CreateCommand("UPDATE todo_items set title=@name, description=@description WHERE id = @id");
            cmd.AddParameter("name", item.Title);
            cmd.AddParameter("description", item.Description);
            cmd.AddParameter("id", item.Id);

Best of all, the code becomes driver independent. (You can switch between SQLiteConnection, SqlClientConnection or whatever without any code modifications).