CSharpFeeds - All your C# feeds in one place.

Sponsors

Monday, February 02, 2009

The MVC platform: creating a new HTML helper

by luisabreu via LA.NET [EN] on 2/2/2009 2:36:36 PM

Yesterday I’ve presented the main properties and methods of the TagBuilder class. Today, we’re going to reuse those ideas and build an extension method that renders an image inside an anchor (unfortunately, the image helper methods are defined in the futures assembly and the current release of the anchors won’t let you pass HTML for its content). Before going on, I’d like to say two things:

  1. This isn’t going to be a complete example. What I mean by this is that I’m going to pick up a general form of helper and implement only that form (so, you won’t have all those overloads you generally end up having with the official methods);
  2. If I had to implement this scenario in the real world, I’d probably go with “straight HTML” and I’d use the Url helper methods for getting the correct urls.

Having said this, I still believe that building a simple example like this is good and might help you get a better grasp of the code used by the helpers. The idea is to have something like this:

<%=
   Html.ActionLinkWithImage(
           "~/Content/add.png", //relative image url
           "About", //controller action
           "Home", //controller
           null , //route values
           null, //link html attributes
           new { style = "border: none"} //image html attributes
           )
%>

As I said, I’m picking only one form (based on the anonymous object syntax) and I’m using it through out this example. You should be able to extend this sample with more overloads that give you more options and reduce the code you need to write if you’re note interested in setting all these parameters. Let’s see how easy it is to build this helper. Here’s the code:

public static class ImageWithAnchorHelper {
    public static string ActionLinkWithImage( this HtmlHelper htmlHelper,
                                     String relativeImageUrl,
                                     String actionName,
                                     String controllerName,
                                     Object routeValues,
                                     Object linkHtmlAttributes,
                                     Object imageHtmlAttributes )  {
        var url = GetUrlForLink( htmlHelper,
                                 actionName,
                                 controllerName,
                                 routeValues,
                                 null,
                                 null,
                                 null );

        var tagBuilder = BuildTagForLink(
                                    htmlHelper,
                                    GetImageHtml( htmlHelper, relativeImageUrl, imageHtmlAttributes ),
                                    linkHtmlAttributes, url );
        return tagBuilder.ToString( TagRenderMode.Normal );
    }
    private static TagBuilder BuildTagForLink( HtmlHelper htmlHelper,
                                               String imageHtml,
                                               Object linkHtmlAttributes,
                                               String url ) {
        var tagBuilder = new TagBuilder( "a" ) {
                                     InnerHtml = imageHtml
                         };
        tagBuilder.MergeAttributes( new RouteValueDictionary( linkHtmlAttributes ) );
        tagBuilder.MergeAttribute( "href", url );
        return tagBuilder;
    }

    private static String GetImageHtml( HtmlHelper helper,
                                        String relativeImageUrl,
                                        Object imageHtmlAttributes )  {
        var imageUrl = new UrlHelper( helper.ViewContext.RequestContext ).Content( relativeImageUrl );
        var imageTag = new TagBuilder( "img" );
        imageTag.MergeAttribute( "src", imageUrl, true );
        imageTag.MergeAttributes( new RouteValueDictionary( imageHtmlAttributes ), true );
        return imageTag.ToString( TagRenderMode.SelfClosing );
    }

    private static String GetUrlForLink( HtmlHelper html,
                                         String actionName,
                                         String controllerName,
                                         Object routeValues,
                                         String protocol,
                                         String hostName,
                                         String fragment ) {
        var urlHelper = new UrlHelper( html.ViewContext.RequestContext );
        var url = urlHelper.Action( actionName,
                                    controllerName,
                                    new RouteValueDictionary(routeValues),
                                    protocol,
                                    hostName );
        if( !String.IsNullOrEmpty( fragment ) ) {
            url += String.Concat (“#”, fragment);
        }
        return url;
    }
}

As you can see, the ActionLinkWithImage method extends the HtmlHelper class and is the only public entry point of these helpers (all the other methods are private). The method starts by getting the url for the current action. If you look at the code used by the helpers, you’ll see that they use the GenerateUrl method for getting the current url. Unfortunately, the method is internal and so we can’t use it without resorting to reflection. On the other hand, we can use the UrlHelper class and its Action method. Lets take a closer look at the GetUrlForLink method. As you can see, we start by instantiating a UrlHelper object:

  var url = urlHelper.Action( actionName,
                                    controllerName,
                                    new RouteValueDictionary(routeValues),
                                    protocol,
                                    hostName );

In this case, protocol and hostname are always null, but I’ve decided to leave the necessary code for making the adaptation of the GetUrlForLink easy for future helpers. That’s why I’ve also made the method receive a fragment, even though the main helper doesn’t accept it (as an exercise, you can always add more overloads which use those parameters: the private methods support them so it’s only necessary to add more overloads of the ActionLinkWithImage that receives them). A fragment is always in the form of http://…..#fragment. That’s why you’ve got that String.Concat there for joining an eventual fragment with the current url:

if( !String.IsNullOrEmpty( fragment ) ) {
            url += String.Concat (“#”, fragment);
}

And that’s it for the GetUrlForLink method. Getting the HTML for the inner image is also easy, as you can see by looking at the GetImageHtml method. We start by creating an UrlHelper object for getting the  correct url for the image. The main difference (when compared with the previous snippet) is that, in this case, we’re passing a relative url and so we’re using the Content method:

var imageUrl = new UrlHelper( helper.ViewContext.RequestContext ).Content( relativeImageUrl );

The “hard work” of getting the HTML is done by the TagBuilder class. Notice that I’m simply using the MergeAttribute and MergeAttributes method in order to set the attributes applied to the final IMG tag. The HTML is obtained by calling an overload of the ToString method and passing a value from the TagRenderMode enumeration to generate an image on the form <img … />:

var imageTag = new TagBuilder( "img" );
imageTag.MergeAttribute( "src", imageUrl, true );
imageTag.MergeAttributes( new RouteValueDictionary( imageHtmlAttributes ), true );
return imageTag.ToString( TagRenderMode.SelfClosing );

After having the inner HTML and the correct link url, we delegate the remaining work to the BuildTagForLink method:

private static TagBuilder BuildTagForLink( HtmlHelper htmlHelper,
                                               String imageHtml,
                                               Object linkHtmlAttributes,
                                               String url ) {
        var tagBuilder = new TagBuilder( "a" ) {
                                     InnerHtml = imageHtml
                         };
        tagBuilder.MergeAttributes( new RouteValueDictionary( linkHtmlAttributes ) );
        tagBuilder.MergeAttribute( "href", url );
        return tagBuilder;
}

As you can see, most of the work is done (once  again) by the TagBuilder class. Since we’re passing HTML for the link’s content, we just pass that value to the InnerHtml property without encoding it (as happens with the current Link extension methods). And that’s all that is needed for having a helper that generates HTML. As I’ve said, this is only demo code. In a real work extension method you’re expected to provide more overloads and perform more validations. I’ll leave those modifications for those that intend to make this a “real world” extension method helper.

Keep tuned for more on the ASP.NET MVC.

email it!bookmark it!digg it!

Original Post: The MVC platform: creating a new HTML helper

Subscribe

New Feed

Product Spotlight

Recently Updated Sources

Legal Note

The content of the postings is owned by the respective author. CSharpFeeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on CSharpFeeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.

Advertise with us