Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using TextBoxFor to set HTML attributes that have a namespace prefix

I am converting some existing HTML to work with ASP.NET MVC, and have some forms containing input fields that have additional attributes (that, for now, I need to preserve) that contain a namespace prefix:

<input type="text" foo:required="true" foo:message="Please enter a username" />

I would like to use the TextBoxFor helper method overload that allows htmlAttributes to be specified using collection initializer syntax:

@Html.TextBoxFor(
    model => model.Username,
    new { foo:required="true", foo:message="Please enter a username" })

However, this syntax is not valid, due to the colon in foo:required (etc).

Instead, I am having to use this more verbose dictionary initializer syntax:

@Html.TextBoxFor(
    model => model.Username,
    new Dictionary<string, object>
    {
        { "foo:required", "true" },
        { "foo:message", "Please enter a username" }
    })

Is it possible to use a variation on the first syntax, somehow escaping (for want of a better word) the colons?

Or

Is this a scenario where the TextBoxFor helper method does not really help, and would it be simpler to just keep the existing raw HTML, and add value="@Model.UserName" to the input element?

like image 331
Richard Ev Avatar asked Aug 12 '13 17:08

Richard Ev


1 Answers

The first syntax is using an anonymous object, for which the same rules regarding how to create identifiers in C# apply:

  • You can use any unicode character of the classes Lu, Ll, Lt, Lm, Lo, or Nl
  • You can scape C# keywords using "@" as in new { @class = "foo" };

Since the colon belongs to the Po unicode class it cannot be used in an identifier. (In C# you can use the static method char.GetUnicodeCategory in order to check the class of any character)

Additionally, and only in MVC, when using an anonymous object for the html attributes in the helper, any attribute name with underscores will be replaced by hyphens. This is due to the method HtmlHelper.AnonymousObjectToHtmlAttributes

Back to your case and regarding your options, if those are not too widely used (like in a couple of views) I would consider staying with the Dictionary syntax of the TextBoxFor helper. You will still get the automatic generation of the id\name properties in sync with the model binding, and you will get any other attributes from the model metadata like the unobtrusive validation attributes. (although looking at the attributes you want to preserve, it seems you won´t need the unobtrusive validation ones :) )

However if the id\name will be as simple as the name of the property and you don´t need any other html attributes that would be generated automatically using the helper, then going for the raw HTML makes sense. (as the dictionary syntax is quite ugly)

In case you use it widely across the application, then in my opinion the most sensible approach may be creating your own helper, like @Html.LegacyTextBoxFor(...). That helper will render those legacy attributes you want to kepp, and you can incorporate additionaly logic similar to the standard helpers for id\name attribute creation.

Something like this:

public class FooAttributes
{
    public bool Required { get; set; }
    public string Message { get; set; }
}

public static class FooHelper
{
    public static MvcHtmlString LegacyTextboxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, FooAttributes fooAttributes)
    {
        var fieldName = ExpressionHelper.GetExpressionText(expression);
        var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
        var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);

        var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
        var value = metadata.Model;

        TagBuilder tag = new TagBuilder("input");
        tag.Attributes.Add("name", fullBindingName);
        tag.Attributes.Add("id", fieldId);
        tag.Attributes.Add("type", "text");
        tag.Attributes.Add("value", value == null ? "" : value.ToString());

        if (fooAttributes != null)
        {
            if (fooAttributes.Required) tag.Attributes.Add("foo:required", "true");
            if (!String.IsNullOrEmpty(fooAttributes.Message)) tag.Attributes.Add("foo:message", fooAttributes.Message);
        }

        return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
    }
}

That can be used as:

@Html.LegacyTextboxFor(model => model.UserName, new FooAttributes {Required=true, Message="Please enter a value." })

And will generate this html:

<input foo:message="Please enter a value." foo:required="true" id="UserName" name="UserName" type="text" value="">

And once you have your own helper, you could add additionaly logic, for example logic that will generate those attributes from the model metadata and its data annotation attributes...

I have extended my answer more than intended, but I hope it helps!

like image 191
Daniel J.G. Avatar answered Nov 15 '22 12:11

Daniel J.G.