Click here to Skip to main content
15,884,537 members
Articles / All Topics

The Problem With Extension Methods Used As Part of a Framework

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
16 Sep 2011LGPL31 min read 11K   1  
The problem with extension methods used as part of a framework

Two of my favorite frameworks/libraries use 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 is 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:

C#
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 to 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:

C#
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):

C#
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:

C#
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.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
-- There are no messages in this forum --