ASP.NET MVC - Extending TextBoxFor without re-writ

2020-02-08 20:54发布

问题:

Is there any possible way to extend the basic html helpers (TextBoxFor, TextAreaFor, etc) using extension methods on their output, instead of just re-writing the entire methods completely? For instance, adding in ...

@Html.TextBoxFor( model => model.Name ).Identity("idName")

I know I can achieve this using the following, already..

@Html.TextBoxFor( model => model.Name, new { @id = "idName" })

But that gets clunky and frustrating to manage when you have to start adding a lot of properties. Is there any way to add extensions to these inherently without just passing in htmlAttributes for every little detail?

回答1:

As @AaronShockley says, because TextBoxFor() returns an MvcHtmlString, your only option for developing a 'fluid API' style of amending the output would be to operate on the MvcHtmlStrings returned by the helper methods. A slightly different way of doing this which I think approaches what you're after would be to use a 'property builder' object, like this:

public class MvcInputBuilder
{
    public int Id { get; set; }

    public string Class { get; set; }
}

...and to set up extension methods like this:

public static MvcHtmlString TextBoxFor<TModel, TProp>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProp>> expression,
    params Action<MvcInputBuilder>[] propertySetters)
{
    MvcInputBuilder builder = new MvcInputBuilder();

    foreach (var propertySetter in propertySetters)
    {
        propertySetter.Invoke(builder);
    }

    var properties = new RouteValueDictionary(builder)
        .Select(kvp => kvp)
        .Where(kvp => kvp.Value != null)
        .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

    return htmlHelper.TextBoxFor(expression, properties);
}

You can then do stuff like this in your View:

@this.Html.TextBoxFor(
    model => model.Name,
    p => p.Id = 7,
    p => p.Class = "my-class")

This gives you strong typing and intellisense for input properties, which you could customise for each extension method by adding properties to an appropriate MvcInputBuilder subclass.



回答2:

All of the basic html helpers return an object of type System.Web.Mvc.MvcHtmlString. You can set up extension methods for that class. Here is an example:

public static class MvcHtmlStringExtensions
{
    public static MvcHtmlString If(this MvcHtmlString value, bool check)
    {
        if (check)
        {
            return value;
        }

        return null;
    }

    public static MvcHtmlString Else(this MvcHtmlString value, MvcHtmlString alternate)
    {
        if (value == null)
        {
            return alternate;
        }

        return value;
    }
}

Then you can use these in a view like:

@Html.TextBoxFor(model => model.Name)
     .If(Model.Name.StartsWith("A"))
     .Else(Html.TextBoxFor(model => model.LastName)

To make extension methods that modify attributes on the rendered HTML tag, you'll have to convert the result to a string, and find and replace the value you're looking for.

using System.Text.RegularExpressions;

public static MvcHtmlString Identity(this MvcHtmlString value, string id)
{
    string input = value.ToString();
    string pattern = @"(?<=\bid=")[^"]*";
    string newValue = Regex.Replace(input, pattern, id);
    return new MvcHtmlString(newValue);
}

public static MvcHtmlString Name(this MvcHtmlString value, string id)
{
    string input = value.ToString();
    string pattern = @"(?<=\bname=")[^"]*";
    string newValue = Regex.Replace(input, pattern, id);
    return new MvcHtmlString(newValue);
}

The id and name attributes are always added by the html helpers, but if you want to work with attributes that may not be there (and you'll have to add them instead of just replacing them), you'll need to modify the code.