Jasper Kent
Jasper Kent

Reputation: 3676

What is the tag helper equivalent of Html.EditorFor

If I use Html.EditorFor on a property of type string then the generated element is by default an <input> of type 'text'.

Alternatively, if the property has the attribute [DataType(DataType.MultilineText)] then Html.EditorFor generates a <textarea>.

If I'm using tag helpers, however, then I have to choose the element type for myself, e.g. <input asp-for='MyProperty' />. This can use information about the property to determine the input's type (text, date, email etc), but will always generate an <input>, never a <textarea>, even if I have the [DataType] attribute.

This seems to go against the idea that I can make the change from single line to multiline in one place: the model. Now I have to go through all relevant views and make the change myself.

It seems to me perfectly possible that I could write my own tag helper that could look at the property attributes and decide whether to generate an <input> or <textarea>, but I can't find any reference to a predefined one.

Is there such a predefined tag helper, and if not, is there a reason why?

Upvotes: 7

Views: 3743

Answers (4)

Ciaran Gallagher
Ciaran Gallagher

Reputation: 4020

Expanding on the answer provided by 'Prince Owusu', I have modified it so that the 'Template' property is optional:

[HtmlTargetElement("editor", TagStructure = TagStructure.WithoutEndTag,
    Attributes = ForAttributeName)]
public class EditorTagHelper : TagHelper
{
    private readonly IHtmlHelper _htmlHelper;

    private const string ForAttributeName = "asp-for";
    private const string TemplateAttributeName = "asp-template";

    [HtmlAttributeName(ForAttributeName)]
    public ModelExpression For { get; set; }


    [HtmlAttributeName(TemplateAttributeName)]
    public string Template { get; set; }

    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }

    public EditorTagHelper(IHtmlHelper htmlHelper)
    {
        _htmlHelper = htmlHelper;
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (output == null)
            throw new ArgumentNullException(nameof(output));

        if (!output.Attributes.ContainsName(nameof(Template)))
        {
            output.Attributes.Add(nameof(Template), Template);
        }

        output.SuppressOutput();

        (_htmlHelper as IViewContextAware).Contextualize(ViewContext);

        output.Content.SetHtmlContent(_htmlHelper.Editor(For.Name, Template));

        await Task.CompletedTask;
    }
}

This would be used something like this:

<editor asp-for="MyModel.EmailAddress"/>

Which is equivalent of:

@Html.EditorFor(x => x.MyModel.EmailAddress)

In my usage example, I have not specified a template so ASP.NET will look for a template that matches the type of my 'EmailAddress' property. If I wanted to specify a template I would use it like this :

<editor asp-for="MyModel.EmailAddress" asp-template="MyEmailTemplate"/>

Upvotes: 0

Prince Owusu
Prince Owusu

Reputation: 31

The new way of using tag helper equivalent of Html.EditorFor is just by Using The Old HtmlHelper Inside Tag Helper itself. Take a look at this blog and you might find what you need or use my code instead. http://blog.techdominator.com/article/using-html-helper-inside-tag-helpers.html

 [HtmlTargetElement("editor", TagStructure = TagStructure.WithoutEndTag, 
    Attributes = ForAttributeName + "," + TemplateAttributeName)]
public class EditorTagHelper : TagHelper
{
    private IHtmlHelper _htmlHelper;

    private const string ForAttributeName = "asp-for";
    private const string TemplateAttributeName = "asp-template";

    [HtmlAttributeName(ForAttributeName)]
    public ModelExpression For { get; set; }


    [HtmlAttributeName(TemplateAttributeName)]
    public string Template { get; set; }

    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }

    public EditorTagHelper(IHtmlHelper htmlHelper)
    {
        _htmlHelper = htmlHelper;
    }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (output == null)
            throw new ArgumentNullException(nameof(output));

        output.SuppressOutput();

        (_htmlHelper as IViewContextAware).Contextualize(ViewContext);

        output.Content.SetHtmlContent(_htmlHelper.Editor(For.Name, Template));

        await Task.CompletedTask;
    }
}

Upvotes: 2

animalito maquina
animalito maquina

Reputation: 2414

Up until now there is no tag helper to achieve this but you can create you own similar to this.

    [HtmlTargetElement("editor-for")]
    public class EditorForTagHelper : TagHelper
    {
        private const string TemplateValuesDictionaryName = "template-all-data";
        private const string HtmlAttributesDictionaryName = "html-all-data";

        private IDictionary<string, object> _templateValues;
        private IDictionary<string, string> _htmlAttributes;

        public ModelExpression For
        {
            get; set;
        }

        /// <summary>
        /// Display template name  to use for rendering
        /// </summary>
        public string Template
        {
            get; set;
        }

        /// <summary>
        /// Additional parameters for the template.
        /// </summary>
        [HtmlAttributeName(TemplateValuesDictionaryName, DictionaryAttributePrefix = "template-")]
        public IDictionary<string, object> TemplateValues
        {
            get
            {
                if (_templateValues == null)
                    _templateValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

                return _templateValues;
            }
            set
            {
                _templateValues = value;
            }
        }

        /// <summary>
        /// Additional htmlattributes for the control template
        /// </summary>
        [HtmlAttributeName(HtmlAttributesDictionaryName, DictionaryAttributePrefix = "html-")]
        public IDictionary<string, string> HtmlAttributes
        {
            get
            {
                if (_htmlAttributes == null)
                    _htmlAttributes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

                return _htmlAttributes;
            }
            set
            {
                _htmlAttributes = value;
            }
        }


        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext
        {
            get; set;
        }

        /// <summary>
        /// Creates a new <see cref="DisplayForTagHelper"/>.
        /// </summary>        
        public EditorForTagHelper()
        {
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            if (output == null)
                throw new ArgumentNullException(nameof(output));


            if (For != null)
            {
                TemplateValues.AddOrUpdate("htmlAttributes", HtmlAttributes.ToDictionary(e => e.Key, e => (object)e.Value));
                var control = ViewContext.GenerateEditor(For,For.Name, Template, TemplateValues);
                output.Content.AppendHtml(control);
            }
        }
    }

Upvotes: 0

Chris Pratt
Chris Pratt

Reputation: 239290

For what it's worth, some of what you discuss is actually part of the InputTagHelper. It will dynamically switch between different input types based on the property type (checkbox for bools, etc.). However, as you indicated, it will only ever generate an input, as it's explicitly an input taghelper. There's an entirely different taghelper for textareas. It is technically possible to create a custom tag helper to achieve what you want, but there's a good deal of work that goes into that. For a reference, see the source for InputTagHelper. At this time, it's not possible to call other tag helpers from a tag helper, so you would need to basically merge the entire code for InputTagHelper with that of TextAreaTagHelper into a custom tag helper. You could potentially save yourself a little work by inheriting your custom tag helper from InputTagHelper and then overriding Process to call base.Process() and then customizing the output. However, again, this is more complicated than a simple answer here can go into.

Long and short, yes, this is achievable with a custom tag helper, but it's going to require some work. Personally, I think it's a bit more work than it's worth, considering it's just one case: switching input with textarea for a multiline input, and you can technically still use EditorFor if your prefer.

If you want to go down this path, Microsoft has an article/tutorial that will give you a good start. Other than that, just study the source for the existing helpers.

Upvotes: 1

Related Questions