Click here to Skip to main content
Licence LGPL3
First Posted 16 Sep 2011
Views 5,590
Bookmarked 1 time

The problem with extension methods used as part of a framework.

By | 16 Sep 2011 | Technical Blog
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 …Read more »
A Technical Blog article. View original blog here.[^]

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.

License

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

About the Author

jgauffin

Founder
Gauffin Interactive AB
Sweden Sweden

Member

Follow on Twitter Follow on Twitter
Freelance developer/architect with a passion for code quality, architecture, refactoring, networking and threading.
 
Solid skills in .NET/C#/MVC3
 
Blog: http://blog.gauffin.org

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 16 Sep 2011
Article Copyright 2011 by jgauffin
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid