How can I pass string value for "asp-for" in asp net 5

37,403

Solution 1

Ok, I managed to get this working. DISCLAIMER: It is super hacky and I have no idea if I've done it in the best way possible. All I know is that it does what you want and it might point you in the right direction.

Firstly, I created a model:

using System.ComponentModel.DataAnnotations;

namespace WebApplication1.Models
{
    public class TestModel
    {
        [Required]
        public string Name { get; set; }

        [Required]
        [EmailAddress]
        [Display(Name = "Email Address")]
        public string Email { get; set; }
    }
}

Then, I made a custom tag helper. This is the horrible bit where the "magic" happens. Specifically the first section of the Process method...

using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.TagHelpers;
using System.Linq;

namespace WebApplication1.TagHelpers
{
    [HtmlTargetElement("edit")]
    public class EditTagHelper : TagHelper
    {
        [HtmlAttributeName("asp-for")]
        public ModelExpression aspFor { get; set; }

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

        protected IHtmlGenerator _generator { get; set; }

        public EditTagHelper(IHtmlGenerator generator)
        {
            _generator = generator;
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            var propName = aspFor.ModelExplorer.Model.ToString();
            var modelExProp = aspFor.ModelExplorer.Container.Properties.Single(x => x.Metadata.PropertyName.Equals(propName));
            var propValue = modelExProp.Model;
            var propEditFormatString = modelExProp.Metadata.EditFormatString;

            var label = _generator.GenerateLabel(ViewContext, aspFor.ModelExplorer,
                propName, propName, new { @class = "col-md-2 control-label", @type = "email" });

            var input = _generator.GenerateTextBox(ViewContext, aspFor.ModelExplorer,
                propName, propValue, propEditFormatString, new { @class = "form-control" });

            var validation = _generator.GenerateValidationMessage(ViewContext, aspFor.ModelExplorer, 
                propName, string.Empty, string.Empty, new { @class = "text-danger" });

            var inputParent = new TagBuilder("div");
            inputParent.AddCssClass("col-md-10");
            inputParent.InnerHtml.Append(input);
            inputParent.InnerHtml.Append(validation);

            var parent = new TagBuilder("div");
            parent.AddCssClass("form-group");
            parent.InnerHtml.Append(label);
            parent.InnerHtml.Append(inputParent);

            output.Content.SetContent(parent);
            base.Process(context, output);
        }
    }
}

NB: To make the custom TagHelper work, you need to add a line into the _ViewImports.cshtml file, like this (replace WebApplication1 with your namespace):

@addTagHelper "*, WebApplication1"

I changed my action to this, to sort of match yours (maybe you can use reflection to get your model property names here?):

public IActionResult Index()
{
    var propertyNames = new List<string>()
    {
        "Name",
        "Email"
    };
    ViewData["PropertyList"] = propertyNames;

    var m = new TestModel()
    {
        Name = "huoshan12345",
        Email = "[email protected]"
    };
    return View(m);
}

Then finally, in the view, you can do something like this:

<div class="row">
    @using (Html.BeginForm())
    {
        var propertyNames = (List<string>)ViewData["PropertyList"];
        foreach (string item in propertyNames)
        {
            <edit asp-for="@item"></edit>
        }
        <input type="submit" value="Submit" />
    }
</div>

Solution 2

You can also try this:

 foreach (string item in propertyNames){
     @Html.TextBox(item, value: null, htmlAttributes: new { @class = "form-control" })      
}
Share:
37,403
huoshan12345
Author by

huoshan12345

Updated on January 08, 2020

Comments

  • huoshan12345
    huoshan12345 over 4 years

    I want to write a Edit.cshtml file for an entity with many properties to edit, so I have to write the following codes many times:

    <div class="form-group">
        <label asp-for="Email" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Email" class="form-control" />
            <span asp-validation-for="Email" class="text-danger"></span>
        </div>
    </div>
    

    Actually, there are many entities so that I have to write many Edit.cshtml files. I want to make some simplifications

    I want to select some properties of the entity in the controller and use loop to show the properties in the view. For example: In the controller file:

    public IActionResult Edit(string id)
    {
        var model = GetModel(id);
        var propertyNames= new List<string>()
        {
            "Name",
            "Email"
            // add some other property names of the entity 
        };
        ViewData["PropertyList"] = propertyNames;
        return View(model);
    }
    

    In the view file:

    @{
        var propertyNames = (List<string>)ViewData["PropertyList"];
        foreach (string item in propertyNames)
        {
            <div class="form-group">
                <label asp-for="@(item)" class="col-md-2 control-label"></label>
                <div class="col-md-3">
                    <input asp-for="@(item)" class="form-control" />
                    <span asp-validation-for="@(item)" class="text-danger"></span>
                </div>          
            </div>
        }
    }
    

    but it cannot work, since it generates wrong codes. It seems that I cannot pass a string value for "asp-for" tag helper.

    For example, if I change the code of top to this:

    @{
        string e = "Email";
        <div class="form-group">
            <label asp-for="@e" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="@e" class="form-control" />
                <span asp-validation-for="@e" class="text-danger"></span>
            </div>
        </div>
    }
    

    The code above will generate this:

    <div class="form-group">
        <label class="col-md-2 control-label" for="e">e</label>
        <div class="col-md-10">
            <input class="form-control" type="text" id="e" name="e" value="Email" />
            <span class="text-danger field-validation-valid" data-valmsg-for="e" data-valmsg-replace="true"></span>
        </div>
    </div>
    

    The expected code is:

    <div class="form-group">
        <label class="col-md-2 control-label" for="Email">Email</label>
        <div class="col-md-10">
            <input class="form-control" type="email" data-val="true" data-val-email="Email &#x5B57;&#x6BB5;&#x4E0D;&#x662F;&#x6709;&#x6548;&#x7684;&#x7535;&#x5B50;&#x90AE;&#x4EF6;&#x5730;&#x5740;&#x3002;" data-val-required="Email &#x5B57;&#x6BB5;&#x662F;&#x5FC5;&#x9700;&#x7684;&#x3002;" id="Email" name="Email" value="" />
            <span class="text-danger field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
        </div>
    </div>
    

    How should I do?

    Is it possible in razor?

  • Ivelin Ivanov
    Ivelin Ivanov over 8 years
    If I forgot something you can write me a comment
  • huoshan12345
    huoshan12345 over 8 years
    Actually I have tried this. I convert all property names to a list of LambdaExpression and pass each of them to the methods like "Html.LabelFor". However it doesnot work, too, because the type argument of the method cannot be inferred from the usage and it throws an exception.
  • huoshan12345
    huoshan12345 over 8 years
    WOW. Thank you very much. Actually, you also answer another question about taghelper which is puzzling me. ----stackoverflow.com/questions/33649181/…
  • clockwiseq
    clockwiseq over 6 years
    Your answer guided me to my answer. Will reference this answer.
  • x19
    x19 about 6 years
    In ASP.NET Core 2, doesn't work. GenerateValidationMessage require 6 parameters and second parameter musst be ModelExplorer type. how can I pass ModelExplorer to method?