119

What is the best way to replace links with images using Razor in MVC3. I simply doing this at the moment:

<a href="@Url.Action("Edit", new { id=MyId })"><img src="../../Content/Images/Image.bmp", alt="Edit" /></a> 

Is there a better way?

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
davy
  • 4,474
  • 10
  • 48
  • 71
  • 15
    Not directly related, but I would strongly suggest you use PNG or JPG files (depending on the image content) instead of BMP files. And like @jgauffin suggested, also try to use application relative paths (`~/Content`). The path `../../Content` might not be valid from different routes (e.g. `/`, `/Home`, `/Home/Index`). – Lucas Feb 04 '11 at 19:20
  • Thanks Lucas. I do use png but the advice for using URL.Content is what I was looking for. vote up :) – davy Feb 07 '11 at 14:52

10 Answers10

217

You can create an extension method for HtmlHelper to simplify the code in your CSHTML file. You could replace your tags with a method like this:

// Sample usage in CSHTML
@Html.ActionImage("Edit", new { id = MyId }, "~/Content/Images/Image.bmp", "Edit")

Here is a sample extension method for the code above:

// Extension method
public static MvcHtmlString ActionImage(this HtmlHelper html, string action, object routeValues, string imagePath, string alt)
{
    var url = new UrlHelper(html.ViewContext.RequestContext);

    // build the <img> tag
    var imgBuilder = new TagBuilder("img");
    imgBuilder.MergeAttribute("src", url.Content(imagePath));
    imgBuilder.MergeAttribute("alt", alt);
    string imgHtml = imgBuilder.ToString(TagRenderMode.SelfClosing);

    // build the <a> tag
    var anchorBuilder = new TagBuilder("a");
    anchorBuilder.MergeAttribute("href", url.Action(action, routeValues));
    anchorBuilder.InnerHtml = imgHtml; // include the <img> tag inside
    string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);

    return MvcHtmlString.Create(anchorHtml);
}
Lucas
  • 17,277
  • 5
  • 45
  • 40
  • This is nice, I also added an overload which allows you to specify the controller for the action. Great work! – Banford Apr 07 '11 at 08:55
  • 5
    Excellent snippet. Anyone wanting to use this with T4MVC just has to change the type of `routeValues` to `ActionResult` and then in the `url.Action` function change `routeValues` to `routeValues.GetRouteValueDictionary()` – JConstantine Aug 17 '11 at 07:52
  • This might be a stupid question. But how do you access your extension method from your view? Should the method be placed somewhere special? – Kasper Skov Sep 02 '11 at 08:02
  • 12
    @Kasper Skov: Place the method in a static class, then reference that class' namespace in the Web.config in the `/configuration/system.web/pages/namespaces` element. – Umar Farooq Khawaja Sep 12 '11 at 00:16
  • MVC newbie here. Got the extension method working, thanks for that. If I need to change the size of the graphic how do I do that? Do I place this ActionImage in a specific tag? What's the best way to do that? – strattonn Dec 02 '11 at 22:34
  • 4
    Nice!, instead of `alt`, I accept an object to receive html properties using an anonymous object then `var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);` and finally `foreach (var attr in attributes){ imgBuilder.MergeAttribute(attr.Key, attr.Value.ToString());}` – guzart Dec 04 '11 at 05:33
  • @UberNeet Solved my size issue as well, I just added style = "height:150px;" to the htmlAttribute. – strattonn Dec 05 '11 at 16:07
  • That code doesn't quite work for me and I'm not sure why it works for you guys. As soon as it executes the line anchorBuilder.InnerHtml = imgHtml I can see that the InnerHtml property gets html encoded so the final page renders with the text of the image tag rather than showing the image. According to the docs for TagBuilder the SetInnerText method always encodes. Did that change between Feb 2 of last year and now? – Steve Hiner Jan 10 '12 at 22:33
  • where to put this extension method for it to work? (I mean in what location in project?) – Ante Feb 10 '12 at 10:35
  • 7
    I couldn't get this working until I realised that because I'm using Areas a reference to the class's namespace (as pointed out by Umar) needs to be added to ALL web.config files in the Views folder for all Areas as well as the top level `/Views` folder – Mark_Gibson Apr 03 '12 at 11:03
  • 2
    If you only need this in a single page, instead of changing the Web.config files, you can add a @using statement in the .cshtml and reference the namespace – JML Jun 30 '12 at 16:01
  • why don't you use css class .Edit { background: url('../images/edit.png') no-repeat right; display: inline-block; height: 16px; width: 16px; }var url = new UrlHelper(html.ViewContext.RequestContext); var anchorBuilder = new TagBuilder("a"); anchorBuilder.MergeAttribute("href", url.Action(action, routeValues)); anchorBuilder.AddCssClass(styleClass); //anchorBuilder.InnerHtml = imgHt tag inside string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal); return MvcHtmlString.Create(anchorHtml); – dnxit Mar 06 '13 at 21:55
64

You can use Url.Content which works for all links as it translates the tilde ~ to the root uri.

<a href="@Url.Action("Edit", new { id=MyId })">
    <img src="@Url.Content("~/Content/Images/Image.bmp")", alt="Edit" />
</a>
jgauffin
  • 99,844
  • 45
  • 235
  • 372
24

Building on Lucas's answer above, this is an overload that takes a controller name as parameter, similar to ActionLink. Use this overload when your image links to an Action in a different controller.

// Extension method
public static MvcHtmlString ActionImage(this HtmlHelper html, string action, string controllerName, object routeValues, string imagePath, string alt)
{
    var url = new UrlHelper(html.ViewContext.RequestContext);

    // build the <img> tag
    var imgBuilder = new TagBuilder("img");
    imgBuilder.MergeAttribute("src", url.Content(imagePath));
    imgBuilder.MergeAttribute("alt", alt);
    string imgHtml = imgBuilder.ToString(TagRenderMode.SelfClosing);

    // build the <a> tag
    var anchorBuilder = new TagBuilder("a");

    anchorBuilder.MergeAttribute("href", url.Action(action, controllerName, routeValues));
    anchorBuilder.InnerHtml = imgHtml; // include the <img> tag inside
    string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);

    return MvcHtmlString.Create(anchorHtml);
}
Crake
  • 1,661
  • 1
  • 13
  • 30
10

Well, you could use @Lucas solution, but there's also another way.

 @Html.ActionLink("Update", "Update", *Your object value*, new { @class = "imgLink"})

Now, add this class on a CSS file or in your page:

.imgLink
{
  background: url(YourImage.png) no-repeat;
}

With that class, any link will have your desired image.

AdrianoRR
  • 1,131
  • 1
  • 17
  • 36
  • 2
    @KasperSkov I forgot this little problem. For some reason, this particular override of actionLink helper, doesn't work with the example above. You have to the `ControllerName` of your action. Like this: `@Html.ActionLink("Update", "Update", "*Your Controller*",*object values*, new {@class = "imgLink"})` – AdrianoRR Sep 03 '11 at 11:36
3

This turned out to be a very useful thread.

For those who are allergic to curly braces, here is the VB.NET version of Lucas' and Crake's answers:

Public Module ActionImage
    <System.Runtime.CompilerServices.Extension()>
    Function ActionImage(html As HtmlHelper, Action As String, RouteValues As Object, ImagePath As String, AltText As String) As MvcHtmlString

        Dim url = New UrlHelper(html.ViewContext.RequestContext)

        Dim imgHtml As String
        'Build the <img> tag
        Dim imgBuilder = New TagBuilder("img")
        With imgBuilder
            .MergeAttribute("src", url.Content(ImagePath))
            .MergeAttribute("alt", AltText)
            imgHtml = .ToString(TagRenderMode.Normal)
        End With

        Dim aHtml As String
        'Build the <a> tag
        Dim aBuilder = New TagBuilder("a")
        With aBuilder
            .MergeAttribute("href", url.Action(Action, RouteValues))
            .InnerHtml = imgHtml 'Include the <img> tag inside
            aHtml = aBuilder.ToString(TagRenderMode.Normal)
        End With

        Return MvcHtmlString.Create(aHtml)

    End Function

    <Extension()>
    Function ActionImage(html As HtmlHelper, Action As String, Controller As String, RouteValues As Object, ImagePath As String, AltText As String) As MvcHtmlString

        Dim url = New UrlHelper(html.ViewContext.RequestContext)

        Dim imgHtml As String
        'Build the <img> tag
        Dim imgBuilder = New TagBuilder("img")
        With imgBuilder
            .MergeAttribute("src", url.Content(ImagePath))
            .MergeAttribute("alt", AltText)
            imgHtml = .ToString(TagRenderMode.Normal)
        End With

        Dim aHtml As String
        'Build the <a> tag
        Dim aBuilder = New TagBuilder("a")
        With aBuilder
            .MergeAttribute("href", url.Action(Action, Controller, RouteValues))
            .InnerHtml = imgHtml 'Include the <img> tag inside
            aHtml = aBuilder.ToString(TagRenderMode.Normal)
        End With

        Return MvcHtmlString.Create(aHtml)

    End Function

End Module
dansan
  • 465
  • 5
  • 16
1

To add to all the Awesome work started by Luke I am posting one more that takes a css class value and treats class and alt as optional parameters (valid under ASP.NET 3.5+). This will allow more functionality but reduct the number of overloaded methods needed.

// Extension method
    public static MvcHtmlString ActionImage(this HtmlHelper html, string action,
        string controllerName, object routeValues, string imagePath, string alt = null, string cssClass = null)
    {
        var url = new UrlHelper(html.ViewContext.RequestContext);

        // build the <img> tag
        var imgBuilder = new TagBuilder("img");
        imgBuilder.MergeAttribute("src", url.Content(imagePath));
        if(alt != null)
            imgBuilder.MergeAttribute("alt", alt);
        if (cssClass != null)
            imgBuilder.MergeAttribute("class", cssClass);

        string imgHtml = imgBuilder.ToString(TagRenderMode.SelfClosing);

        // build the <a> tag
        var anchorBuilder = new TagBuilder("a");

        anchorBuilder.MergeAttribute("href", url.Action(action, controllerName, routeValues));
        anchorBuilder.InnerHtml = imgHtml; // include the <img> tag inside
        string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);

        return MvcHtmlString.Create(anchorHtml);
    }
Zack Jannsen
  • 622
  • 7
  • 17
  • Also, for anyone new to MVC, helpful hint - the value of routeValue shoudl be @RouteTable.Routes["Home"] or whatever your "route" id is in teh RouteTable. – Zack Jannsen Jan 24 '13 at 03:55
1

slide modification changed Helper

     public static IHtmlString ActionImageLink(this HtmlHelper html, string action, object routeValues, string styleClass, string alt)
    {
        var url = new UrlHelper(html.ViewContext.RequestContext);
        var anchorBuilder = new TagBuilder("a");
        anchorBuilder.MergeAttribute("href", url.Action(action, routeValues));
        anchorBuilder.AddCssClass(styleClass);
        string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);

        return new HtmlString(anchorHtml);
    }

CSS Class

.Edit {
       background: url('../images/edit.png') no-repeat right;
       display: inline-block;
       height: 16px;
       width: 16px;
      }

Create the link just pass the class name

     @Html.ActionImageLink("Edit", new { id = item.ID }, "Edit" , "Edit") 
dnxit
  • 7,118
  • 2
  • 30
  • 34
1

This extension method also works (to be placed in an public static class):

    public static MvcHtmlString ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, object routeValues, AjaxOptions ajaxOptions)
    {
        var builder = new TagBuilder("img");
        builder.MergeAttribute("src", imageUrl);
        builder.MergeAttribute("alt", altText);
        var link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions);
        return new MvcHtmlString( link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)) );
    }
Diego
  • 18,035
  • 5
  • 62
  • 66
0

I have joined the answer from Lucas and "ASP.NET MVC Helpers, Merging two object htmlAttributes together" and plus controllerName to following code:

// Sample usage in CSHTML

 @Html.ActionImage("Edit",
       "EditController"
        new { id = MyId },
       "~/Content/Images/Image.bmp",
       new { width=108, height=129, alt="Edit" })

And the extension class for the code above:

using System.Collections.Generic;
using System.Reflection;
using System.Web.Mvc;

namespace MVC.Extensions
{
    public static class MvcHtmlStringExt
    {
        // Extension method
        public static MvcHtmlString ActionImage(
          this HtmlHelper html,
          string action,
          string controllerName,
          object routeValues,
          string imagePath,
          object htmlAttributes)
        {
            //https://stackoverflow.com/questions/4896439/action-image-mvc3-razor
            var url = new UrlHelper(html.ViewContext.RequestContext);

            // build the <img> tag
            var imgBuilder = new TagBuilder("img");
            imgBuilder.MergeAttribute("src", url.Content(imagePath));

            var dictAttributes = htmlAttributes.ToDictionary();

            if (dictAttributes != null)
            {
                foreach (var attribute in dictAttributes)
                {
                    imgBuilder.MergeAttribute(attribute.Key, attribute.Value.ToString(), true);
                }
            }                        

            string imgHtml = imgBuilder.ToString(TagRenderMode.SelfClosing);

            // build the <a> tag
            var anchorBuilder = new TagBuilder("a");
            anchorBuilder.MergeAttribute("href", url.Action(action, controllerName, routeValues));
            anchorBuilder.InnerHtml = imgHtml; // include the <img> tag inside            
            string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);

            return MvcHtmlString.Create(anchorHtml);
        }

        public static IDictionary<string, object> ToDictionary(this object data)
        {
            //https://stackoverflow.com/questions/6038255/asp-net-mvc-helpers-merging-two-object-htmlattributes-together

            if (data == null) return null; // Or throw an ArgumentNullException if you want

            BindingFlags publicAttributes = BindingFlags.Public | BindingFlags.Instance;
            Dictionary<string, object> dictionary = new Dictionary<string, object>();

            foreach (PropertyInfo property in
                     data.GetType().GetProperties(publicAttributes))
            {
                if (property.CanRead)
                {
                    dictionary.Add(property.Name, property.GetValue(data, null));
                }
            }
            return dictionary;
        }
    }
}
Community
  • 1
  • 1
Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
0

This would be work very fine

<a href="<%:Url.Action("Edit","Account",new {  id=item.UserId }) %>"><img src="../../Content/ThemeNew/images/edit_notes_delete11.png" alt="Edit" width="25px" height="25px" /></a>
kleopatra
  • 51,061
  • 28
  • 99
  • 211