Search code examples
c#asp.netasp.net-mvcrazortag-helpers

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


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?


Solution

  • 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 = "test@test.net"
        };
        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>