339

I am trying to use HTML5 data- attributes in my ASP.NET MVC 1 project. (I am a C# and ASP.NET MVC newbie.)

 <%= Html.ActionLink("« Previous", "Search",
     new { keyword = Model.Keyword, page = Model.currPage - 1},
     new { @class = "prev", data-details = "Some Details"   })%>

The "data-details" in the above htmlAttributes give the following error:

 CS0746: Invalid anonymous type member declarator. Anonymous type members 
  must be declared with a member assignment, simple name or member access.

It works when I use data_details, but I guess it need to be starting with "data-" as per the spec.

My questions:

  • Is there any way to get this working and use HTML5 data attributes with Html.ActionLink or similar Html helpers ?
  • Is there any other alternative mechanism to attach custom data to an element? This data is to be processed later by JS.
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Shameem
  • 14,199
  • 13
  • 40
  • 43
  • 5
    this is an old question with outdated answer - users of MVC 3 and above should view this question http://stackoverflow.com/questions/2897733/hyphenated-html-attributes-with-asp-net-mvc – ED-209 Mar 05 '13 at 16:08

8 Answers8

667

This problem has been addressed in ASP.Net MVC 3. They now automatically convert underscores in html attribute properties to dashes. They got lucky on this one, as underscores are not legal in html attributes, so MVC can confidently imply that you'd like a dash when you use an underscore.

For example:

@Html.TextBoxFor(vm => vm.City, new { data_bind = "foo" })

will render this in MVC 3:

<input data-bind="foo" id="City" name="City" type="text" value="" />

If you're still using an older version of MVC, you can mimic what MVC 3 is doing by creating this static method that I borrowed from MVC3's source code:

public class Foo {
    public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes) {
        RouteValueDictionary result = new RouteValueDictionary();
        if (htmlAttributes != null) {
            foreach (System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes)) {
                result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
            }
        }
        return result;
    }
}

And then you can use it like this:

<%: Html.TextBoxFor(vm => vm.City, Foo.AnonymousObjectToHtmlAttributes(new { data_bind = "foo" })) %>

and this will render the correct data-* attribute:

<input data-bind="foo" id="City" name="City" type="text" value="" />
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
  • 6
    This doesn't work for me for some reason. View source shows data_*. Using MVC3. Any ideas? – Simon Hartcher Jul 28 '11 at 03:37
  • Hi Simon, did you solve your problem? If not, can you provide your code that's causing you the problem? – Johnny Oshika Sep 02 '11 at 00:32
  • Still no luck with `WebGrid.GetHtml(htmlAttributes: new { data_some : "thing" })`. :'( – Rubens Mariuzzo Nov 26 '12 at 16:04
  • +1 for specifying why underscores would work. It didn't occur to me that underscores were not valid in attribute names for HTML! – Umar Farooq Khawaja Nov 28 '12 at 02:06
  • 2
    @RubensMariuzzo this isn't baked in to the `RouteValueDictionary` but into MVC3's `Html.Something()` methods. It's possible that `WebGrid` hasn't been upgraded in the same way, or you could check the version on the `System.Web.Helpers.dll` – Keith Apr 19 '13 at 08:11
  • @Keith - I agree with you on where the magic's at (by experience); do you know where that is documented? Reflector? – mlhDev Oct 30 '13 at 13:41
  • @Matthew I'm afraid I don't know - I just took the source apart. – Keith Oct 30 '13 at 16:18
  • This `AnonymousObjectToHtmlAttributes` method is publicly available as a static method on the `HtmlHelper` class. – StriplingWarrior Nov 22 '13 at 21:11
  • This is undocumented besides on StackOverflow - this website puts MSDN to shame. – Shawn J. Molloy Jun 16 '14 at 01:34
  • Convenient - but i wish there was a better solution as it breaks simple "find text" in source code files when the same thing is spelt differently depending on being markup, C# or Javascript. – Oskar Berggren Jul 05 '18 at 08:49
118

Update: MVC 3 and newer versions have built-in support for this. See JohnnyO's highly upvoted answer below for recommended solutions.

I do not think there are any immediate helpers for achieving this, but I do have two ideas for you to try:

// 1: pass dictionary instead of anonymous object
<%= Html.ActionLink( "back", "Search",
    new { keyword = Model.Keyword, page = Model.currPage - 1},
    new Dictionary<string,Object> { {"class","prev"}, {"data-details","yada"} } )%>

// 2: pass custom type decorated with descriptor attributes
public class CustomArgs
{
    public CustomArgs( string className, string dataDetails ) { ... }

    [DisplayName("class")]
    public string Class { get; set; }
    [DisplayName("data-details")]
    public string DataDetails { get; set; }
}

<%= Html.ActionLink( "back", "Search",
    new { keyword = Model.Keyword, page = Model.currPage - 1},
    new CustomArgs( "prev", "yada" ) )%>

Just ideas, haven't tested it.

Morten Mertner
  • 9,414
  • 4
  • 39
  • 56
61

It's even easier than everything suggested above. Data attributes in MVC which include dashes (-) are catered for with the use of underscore (_).

<%= Html.ActionLink("« Previous", "Search",
 new { keyword = Model.Keyword, page = Model.currPage - 1},
 new { @class = "prev", data_details = "Some Details"   })%>

I see JohnnyO already mentioned this.

Oliver
  • 35,233
  • 12
  • 66
  • 78
  • 13
    The method responsible for this is: HtmlHelper.AnonymousObjectToHtmlAttributes(object), in case anyone is wondering why their custom extension won't replace the underscore with an hyphen. – Nikkelmann Jun 15 '13 at 14:25
  • 1
    This really needs voting up as it's by far the simplest answer and works perfectly! – Dan Diplo Aug 06 '13 at 12:29
28

In mvc 4 Could be rendered with Underscore(" _ ")

Razor:

@Html.ActionLink("Vote", "#", new { id = item.FileId, }, new { @class = "votes", data_fid = item.FileId, data_jid = item.JudgeID, })

Rendered Html

<a class="votes" data-fid="18587" data-jid="9" href="/Home/%23/18587">Vote</a>
mzonerz
  • 1,220
  • 15
  • 22
  • hi, is there a way to POST the data-fid to the controller, via an Ajax or submit. wondering how to leverage the data. For e.g if I had a `data-date` in the attribute, How could I post it to the controller/action? Also what is %23 there – Transformer Jan 02 '18 at 10:00
  • Yes use form collection and access it with ID or Name, and %23 is # – mzonerz Jan 10 '18 at 10:11
4

You can implement this with a new Html helper extension function which will then be used similarly to the existing ActionLinks.

public static MvcHtmlString ActionLinkHtml5Data(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes, object htmlDataAttributes)
{
    if (string.IsNullOrEmpty(linkText))
    {
        throw new ArgumentException(string.Empty, "linkText");
    }

    var html = new RouteValueDictionary(htmlAttributes);
    var data = new RouteValueDictionary(htmlDataAttributes);

    foreach (var attributes in data)
    {
        html.Add(string.Format("data-{0}", attributes.Key), attributes.Value);
    }

    return MvcHtmlString.Create(HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, null, actionName, controllerName, new RouteValueDictionary(routeValues), html));
}

And you call it like so ...

<%: Html.ActionLinkHtml5Data("link display", "Action", "Controller", new { id = Model.Id }, new { @class="link" }, new { extra = "some extra info" })  %>

Simples :-)

edit

bit more of a write up here

WestDiscGolf
  • 4,098
  • 2
  • 32
  • 47
3

I ended up using a normal hyperlink along with Url.Action, as in:

<a href='<%= Url.Action("Show", new { controller = "Browse", id = node.Id }) %>'
  data-nodeId='<%= node.Id %>'>
  <%: node.Name %>
</a>

It's uglier, but you've got a little more control over the a tag, which is sometimes useful in heavily AJAXified sites.

HTH

Keith Williams
  • 2,257
  • 3
  • 19
  • 29
0

I do not like use pure "a" tag, too much typing. So I come with solution. In view it look

<%: Html.ActionLink(node.Name, "Show", "Browse", 
                    Dic.Route("id", node.Id), Dic.New("data-nodeId", node.Id)) %>

Implementation of Dic class

public static class Dic
{
    public static Dictionary<string, object> New(params object[] attrs)
    {
        var res = new Dictionary<string, object>();
        for (var i = 0; i < attrs.Length; i = i + 2)
            res.Add(attrs[i].ToString(), attrs[i + 1]);
        return res;
    }

    public static RouteValueDictionary Route(params object[] attrs)
    {
        return new RouteValueDictionary(Dic.New(attrs));
    }
}
-2

You can use it like this:

In Mvc:

@Html.TextBoxFor(x=>x.Id,new{@data_val_number="10"});

In Html:

<input type="text" name="Id" data_val_number="10"/>
Micho
  • 3,929
  • 13
  • 37
  • 40