51

HTML5 appears to support a new range of input fields for things such as:

  • Numbers
  • Email addresses
  • Colors
  • URLs
  • Numeric range (via a slider)
  • Dates
  • Search boxes

Has anyone implemented HtmlHelper extension methods for ASP.NET MVC that generates these yet? It's possible to do this using an overload that accepts htmlAttributes, such as:

Html.TextBoxFor(model => model.Foo, new { type="number", min="0", max="100" })

But that's not as nice (or typesafe) as:

Html.NumericInputFor(model => model.Foo, min:0, max:100)
Matt
  • 74,352
  • 26
  • 153
  • 180
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • What's the meaning of "typesafe" for the examples you share here? I can see the difference in both codes, but I don't get why one is typesafe and the other one is not. – carloswm85 Apr 19 '22 at 14:18
  • @carloswm85 the first snippet uses an anonymous type. It's not typesafe because the compiler doesn't check the names of any of the properties. You could mistype `min` as `man` and your code would compile correctly but not produce the desired result, because of a typing error. If it were typesafe, this error would be caught. – Drew Noakes Apr 23 '22 at 12:43
  • So that happens in the first snippet because it is taking an object such as `new { type="number", min="0", max="100" }` as the second parameter? That's why it is not typesafe. Unlike the second snippet, where parameters are orderly set and described in, let's say, "individual parameters". – carloswm85 Apr 25 '22 at 11:02
  • Yes, that's right. The compiler doesn't validate anything about `new { ... }`. – Drew Noakes Apr 28 '22 at 01:31

6 Answers6

54

Just a heads up that many of these are now incorporated into MVC4 by using the DataType attribute.

As of this work item you can use:

public class MyModel 
{
    // Becomes <input type="number" ... >
    public int ID { get; set; }

    // Becomes <input type="url" ... >
    [DataType(DataType.Url)]
    public string WebSite { get; set; }

    // Becomes <input type="email" ... >
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    // Becomes <input type="tel" ... >
    [DataType(DataType.PhoneNumber)]
    public string PhoneNumber { get; set; }

    // Becomes <input type="datetime" ... >
    [DataType(DataType.DateTime)]
    public DateTime DateTime { get; set; }

    // Becomes <input type="date" ... >
    [DataType(DataType.Date)]
    public DateTime Date { get; set; }

    // Becomes <input type="time" ... >
    [DataType(DataType.Time)]
    public DateTime Time { get; set; }
}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Paul Hiles
  • 9,558
  • 7
  • 51
  • 76
  • @Drew - believe date, datetime and time are included in that work item (they are certainly part of mvc4 release). why do you say otherwise? – Paul Hiles May 13 '13 at 14:27
  • I misinterpreted the comment: _We need to do this automatically for tel, url, email, datetime, date, time, and number._ I've linked to the docs and extended the sample code. Nice find, thanks. – Drew Noakes May 13 '13 at 14:34
23

Check out the ASP.net MVC HTML5 Helpers Toolkit

Mauricio Scheffer
  • 98,863
  • 23
  • 192
  • 275
15

Easiest way is to simply add type="Email" as an html attribute. It overrides the default type="text". Here is an example with a html5 required validator also:

@Html.TextBox("txtEmail", "", 
    new { placeholder = "email address", 
          @type="email", 
          @required = ""
    })
dalcam
  • 1,027
  • 11
  • 28
  • That's pretty much what I show in the original question. I was looking for a more typesafe solution. – Drew Noakes Jun 13 '14 at 07:25
  • Hi @Drew - your are completely right, by the time i read the answers I had forgotten your original question - sorry! – dalcam Jun 13 '14 at 11:09
  • No worries. Your code definitely works, but I would like to avoid using anonymous types, both for performance and because they're not checked by the compiler for correctness. – Drew Noakes Jun 13 '14 at 21:14
  • This answer definitely worth being here. Googled this question up - found this solution – Alex from Jitbit Mar 19 '15 at 18:57
  • 1
    This is the answer I found most easy to implement. I didn't want to have to use `Editor` helper, and this answer supports that inclination. – Nick Jan 22 '16 at 17:10
11

What i don't like about DataTypes Attributes is that u have to use EditorFor in the view. Then, you can't use htmlAttributes to decorate your tag. There are other solutions but i prefer this way.

In this example i only extended the signature i use the most.

So in the class:

using System.Linq.Expressions;
namespace System.Web.Mvc.Html
{
    public static class HtmlExtensions
    {
        public static MvcHtmlString EmailFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, Object htmlAttributes)
        {
            MvcHtmlString emailfor = html.TextBoxFor(expression, htmlAttributes);
            return new MvcHtmlString(emailfor.ToHtmlString().Replace("type=\"text\"", "type=\"email\""));
        }
    }
}

As you see i just changed the type="text" for type="email" and then i can use in my view:

    <div class="form-group">            
        @Html.LabelFor(m => m.Email, new { @class = "col-lg-2 control-label" })
        <div class="col-lg-10">
            @Html.EmailFor(m => m.Email, new { @class = "form-control", placeholder = "Email" })
            @Html.ValidationMessageFor(m => m.Email)                                 
        </div>            
    </div>

And the html source gives:

<div class="form-group">            
    <label class="col-lg-2 control-label" for="Email">Email</label>
    <div class="col-lg-10">
        <input class="form-control" data-val="true" data-val-required="The Email field is required." id="Email" name="Email" placeholder="Email" type="email" value="" />
        <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>                                 
    </div>            
</div> 
RPAlbert
  • 121
  • 1
  • 4
  • 2
    FYI, you can in fact use html attributes with EditorFor, but you would have to write your own template for it. Which is a lot less intrusive than writing a new Html helper. – Erik Funkenbusch Sep 18 '13 at 00:50
  • I agree with u and i did see a few remarks about using templates. Sadly i'm only at the learning stage in MVC right now and didn't take the time to look at Editor and Display templates. – RPAlbert Sep 18 '13 at 03:18
  • 4
    As of MVC 5.1 you can use HTML attributes with EditorFor tags. – Yecats Apr 21 '14 at 17:09
9

Love it when can drive this type of stuff off the model!! I decorated my models with [DataType(DataType.PhoneNumber)], and all but one worked.

I realized the hard way that @Html.TextBoxFor doesn't render the type="<HTML5 type>" but @Html.EditorFor does. Makes sense I guess now that I think about it, but posting this to maybe save others the frustrating few minutes that I just lost;)

Brant Olsen
  • 5,628
  • 5
  • 36
  • 53
Phillip Wells
  • 119
  • 1
  • 3
  • In my case not working with TextBoxFor and EditorFor in both of IE and Chrome, What is the wrong you think? – QMaster Feb 04 '14 at 15:02
2

I found my self wanting the number spinner you get when using <input type='number' /> from an HtmlHelper, and ended up solving it my self.

In a similar fashion to RPAlbert's Html.EmailFor answer above, I started off using the normal Html.TextBoxFor, but then I used LinqToXml to modify the HTML rather than just using a string replace.

The advantage of starting with the Html.TextBoxFor is that you can use of all the client side validation stuff that MVC does for you. In this case I am using the values from the data-val-range attributes to set the min/max attributes needed to constrain the spinner.

public static HtmlString SpinnerFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
    XDocument _xml = XDocument.Parse(html.TextBoxFor(expression, htmlAttributes).ToString());
    XElement _element = _xml.Element("input");

    if (_element != null)
    {
        _element.SetAttributeValue("type", "number");

        if (_element.Attribute("data-val-range-max") != null) 
            _element.SetAttributeValue("max", _element.Attribute("data-val-range-max").Value);

        if (_element.Attribute("data-val-range-min") != null) 
            _element.SetAttributeValue("min", _element.Attribute("data-val-range-min").Value);
    }

    return new HtmlString(_xml.ToString());
}

You would then use it as you would any other HtmlHelper in your views:

@Html.SpinnerFor(model => model.SomeNumber, new { htmlAttribute1 = "SomeValue" })

This was my implementation of it anyway, from you question I can see that you wanted:

@Html.NumericInputFor(model => model.Foo, min:0, max:100)

It would be very simple to tweak my method to do this as follows:

public static HtmlString NumericInputFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, int min, int max)
{
    XDocument _xml = XDocument.Parse(html.TextBoxFor(expression, htmlAttributes).ToString());
    XElement _element = _xml.Element("input");

    if (_element != null)
    {
        _element.SetAttributeValue("type", "number");
        _element.SetAttributeValue("min", min);
        _element.SetAttributeValue("max", max);
    }

    return new HtmlString(_xml.ToString());
}

Basically all I have done is to rename it and provide min/max as arguments rather than getting them from DataAnnotation attributes.

I hope that helps!

Ben
  • 5,525
  • 8
  • 42
  • 66