3

I have two properties in a model class:

public int? IntTest { get; set; }
public decimal? DecimalTest { get; set; }

Which I then render with:

@Html.EditorFor(model => model.IntTest, new { htmlAttributes = new { @class = "form-control"} })
@Html.EditorFor(model => model.DecimalTest, new { htmlAttributes = new { @class = "form-control"} })

I'd expected both of them to render as html inputs of type number, but the decimal one doesn't, I get:

<input class="form-control text-box single-line" data-val="true" data-val-number="The field IntTest must be a number." id="IntTest" name="IntTest" type="number" value="" />
<input class="form-control text-box single-line" data-val="true" data-val-number="The field IntTest must be a number." id="DecimalTest" name="DecimalTest" type="text" value="" />

The decimal value is rendered as type="text" whereas the int is registered as type="number".

This question implies that isn't the expected behaviour so am I doing something wrong?

If that is the expected behaviour, is there any way of changing EditorFor to render all decimals as type="number", rather than having to add type = "number" in the htmlAttributes of every decimal field?

2
  • Do you need to use EditorFor ? Did you consider using TextBoxFor ? Commented Aug 25, 2018 at 19:36
  • @Shyju I'm pretty sure I got the same behaviour with TextBoxFor too - should TextBoxFor generate an input with type = "number"? Commented Aug 25, 2018 at 22:03

1 Answer 1

4

The html you are seeing is the default behavior. The EditorFor() methods use the default templates (unless you have created a custom EditorTemplate for the type) as defined in TemplateHelpers.cs.

For typeof int (and byte and long), it uses the NumberInputTemplate, and for typeof decimal it uses the DecimalTemplate. These templates are defined in DefaultEditorTemplates.cs which are for decimal

internal static string DecimalTemplate(HtmlHelper html)
{
    if (html.ViewContext.ViewData.TemplateInfo.FormattedModelValue == html.ViewContext.ViewData.ModelMetadata.Model)
    {
        html.ViewContext.ViewData.TemplateInfo.FormattedModelValue = String.Format(CultureInfo.CurrentCulture, "{0:0.00}", html.ViewContext.ViewData.ModelMetadata.Model);
    }
    return StringTemplate(html);
}

which in turn calls

internal static string StringTemplate(HtmlHelper html)
{
    return HtmlInputTemplateHelper(html);
}

and for int

internal static string NumberInputTemplate(HtmlHelper html)
{
    return HtmlInputTemplateHelper(html, inputType: "number");
}

Note that the NumberInputTemplate defines the inputType as "number" which adds the type="number" attribute, where as StringTemplate uses the default inputType which generates type="text".

To add type="number" for a decimal, then you need to manually add the attribute, using either

@Html.EditorFor(m => m.DecimalTest, new { htmlAttributes = new { type = "number", @class = "form-control"} })

or

@Html.TextBoxFor(m => m.DecimalTest, new { type = "number", @class = "form-control"})

An alternative would be to create a custom EditorTemplate in the /Views/Shared/EditorTemplates/Decimal.cshtml for typeof decimal, for example

@model decimal?
@{
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(ViewData["htmlAttributes"]);
    if (!attributes.ContainsKey("type"))
    {
         attributes.Add("type", "number");
    }
    string formatString = ViewData.ModelMetadata.DisplayFormatString ?? "{0:N2}";
}
@Html.TextBoxFor(m => m, formatString , attributes)

and in the main view use

@Html.EditorFor(model => model.DecimalTest, new { htmlAttributes = new { @class = "form-control"} })

Another alternative would be to create you own HtmlHelper extension method (say @Html.DecimalFor(...)) to generate the html.

Sign up to request clarification or add additional context in comments.

7 Comments

That's fascinating, has opened up a whole new side of things I didn't know existed and really helped me understand the behaviour. Thanks very much for taking the time to explain it. I hadn't realised the traditional aspnet source code was online, I'm off for a good dig around in there!
Actually, one follow on question; do you have any idea why this is the default behaviour for decimals? Is it to allow for commas as decimal separators perhaps?
I'm guessing it has to do with the precision - how may decimal places to display, and if you had say 2, would you then want the step to be 0.01, which would take forever to increment via the buttons making using it a bit pointless (its not associated with the decimal separator)
Both good points - I'd already killed the spinners via CSS in my code. I've made one change to your code by adding an if statement to check if the type attribute is already there, that means it can then be overridden from code on a case by case basis and won't error if the user has already set the type: if (!attributes.ContainsKey("type")) { attributes.Add("type", "number"); } . If you think that's a good addition by all means update your answer to include it.
@tomRedox. Updated :) - and of course there are numerous other enhancements you could make such as reading any forma string from ModelMetadata and applying that or a default if applicable. But out of interest, if you have removed the spinner, why use type="number"?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.