Extending HTML Helper

2019-07-05 05:52发布

The code I am looking to produce is similar to:

<div class="form-group">
    <label for="email">Email address</label>
    <input type="email" class="form-control" id="email" placeholder="Enter email">
</div>

Using something like this:

@Html.LabelledTextBoxFor(model => model.EmailAddress)

Where the label text get derived from model.EmailAddress

This would be done using Data Annotation e.g.

[Displayname("Email Address]

What is the best way to accomplish this?

Will this affect the auto generated client side JS validation using jquery val?

4条回答
等我变得足够好
2楼-- · 2019-07-05 06:11

I would use @Html.EditorFor template. So create EditorTemplates folder in Views/Shared in your MVC project with a name EmailAddress.cshtml - it will display this template for every [DataType(DataType.EmailAddress)] defined in your model.

So: EmailAddress.cshtml should look sth like this:

<div class="form-group">
<label for="email">@Html.LabelFor(x => x)</label>
@Html.TextBoxFor(x => x, new { @class = "form-control", placeholder = ViewData.ModelMetadata.Watermark })
</div>

Model should look like this:

public class SomeModel {

    [DataType(DataType.EmailAddress)]
    [Display(Name="Some string for LabelFor", Prompt = "Placeholder in input"]
    public string SomeEmail { get; set; }
}

Note here, that I've used Prompt property of Display metadata, which I linked to placeholder via ViewData.ModelMetadata.Watermark in EmailAddress.cshtml. Html.LabelFor will always take property Name from Display and DataType is needed for linking between EditorTemplates and model property.

And invoke all this "magic" by using

@Html.EditorFor(m=>m.SomeEmail)

on a page, where you use SomeMode model.

I hope this does make sense.

查看更多
Summer. ? 凉城
3楼-- · 2019-07-05 06:23

No need to reinvent the wheel. Check out TwitterBootstrapMVC.

The code you'd be writing would look similar to the below:

@Html.Bootstrap().FormGroup().TextBoxFor(model => model.EmailAddress).Placeholder("Enter email")

Disclaimer: I'm the authot of TwitterBootstrapMVC.

TwitterBootstrapMVC for use on Bootstrap 3 is not free. See details on the website.

查看更多
在下西门庆
4楼-- · 2019-07-05 06:34

The Data Annotations go in the viewmodel above the property:

[DisplayName("Email Address")]
public string EmailAddress { get; set; }

Then, in your view, you'd have something like this:

@Html.LabelFor(m => m.EmailAddress)
@Html.TextBoxFor(m => m.EmailAddress, new { @placeholder="Enter email", @class="form-control", @id="email" })
查看更多
▲ chillily
5楼-- · 2019-07-05 06:37

I wrote an extension method, maybe this might help:

public static MvcHtmlString LabelledTextBoxFor<TModel, TResult>(this HtmlHelper<TModel> html, Expression<Func<TModel, TResult>> expression)
{
    ExpressionType type = expression.Body.NodeType;
    if (type == ExpressionType.MemberAccess)
    {
       MemberExpression memberExpression = (MemberExpression) expression.Body;
       var propName = memberExpression.Member.Name;

       var member = memberExpression.Member as PropertyInfo;

       var attributes = member.GetCustomAttributes();

       StringBuilder sb = new StringBuilder();
       foreach (var attribute in attributes)
       {
           if (attribute is DisplayAttribute)
           {
               DisplayAttribute d = attribute as DisplayAttribute;
               var displayName = d.Name;
               sb.Append("<div class=\"form-group\">");
               sb.AppendFormat("<label for=\"{0}\">{1}</label>", propName, displayName);
               sb.AppendFormat(
                        "<input type=\"email\" class=\"form-control\" id=\"{0}\" placeholder=\"Enter email\">",
                        propName);
               sb.Append("</div>");
               return MvcHtmlString.Create(sb.ToString());
             }
         }

     }
       return MvcHtmlString.Create("");
 }

You can use default display attribute to specify display name.There is no need for custom attributes.And you can use this extension like this:

@Html.LabelledTextBoxFor(model => model.EmailAddress)

Note: I have tried myself and it is working correctly.

Update: More simple version

public static MvcHtmlString LabelledTextBoxFor2<TModel, TResult>(this HtmlHelper<TModel> html, Expression<Func<TModel, TResult>> expression)
    {
        ExpressionType type = expression.Body.NodeType;
        if (type == ExpressionType.MemberAccess)
        {
            var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            var displayName = metadata.DisplayName;
            var propName = metadata.PropertyName;

            StringBuilder sb = new StringBuilder();

            sb.Append("<div class=\"form-group\">");
            sb.AppendFormat("<label for=\"{0}\">{1}</label>", propName, displayName);
            sb.AppendFormat(
                        "<input type=\"email\" class=\"form-control\" id=\"{0}\" placeholder=\"Enter email\">",
                        propName);
             sb.Append("</div>");
             return MvcHtmlString.Create(sb.ToString());
        }

        return MvcHtmlString.Create("");
    }
查看更多
登录 后发表回答