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

4

10

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?

Meatball answered 15/12, 2015 at 6:44 Comment(3)
Consider adding sample of what "it generates wrong codes" look like. Your CSHTML looks reasonable.Oas
You need to change your input attribute from "asp-for" to "id" anywaysNattie
@AlexeiLevenkov I added an sample.Meatball
B
11

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>
Bracci answered 15/12, 2015 at 14:23 Comment(4)
WOW. Thank you very much. Actually, you also answer another question about taghelper which is puzzling me. ----#33649681Meatball
Your answer guided me to my answer. Will reference this answer.Miculek
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?Hackbut
Super Jamie, thank you very much! ...even if you gave your answer already eight years ago ;-)Delamination
T
1

You can also try this:

 foreach (string item in propertyNames){
     @Html.TextBox(item, value: null, htmlAttributes: new { @class = "form-control" })      
}
Tactician answered 7/1, 2020 at 17:5 Comment(0)
P
0

Yes, it is possible to write it with razor. think this will help you. If you don't put the "type" attribute it will generate it with type='text'. You can also hard code your data-val attributes, but it is not recommended just replace the '-' with '_' Ex: @data_val_email

<div class="form-group">
  @Html.LabelFor(x=>x.Email)
  <div class="col-md-10">
     @Html.TextBoxFor(x => x.Email, new { @class = "form-control", @type = "email" })
      @Html.ValidateFor(x=>x.Email)
  </div>
</div>
Punchboard answered 15/12, 2015 at 9:32 Comment(2)
If I forgot something you can write me a commentPunchboard
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.Meatball
R
-1

Here's a related technique. I extended the tag helper to inject the required HTML into the page. This works a bit like the ASP.NET MVC EditorTemplate.

Here's the custom tag helper that injects a special partial view

public class MyFormGroupTagHelper : PartialTagHelper
{
    public MyFormGroupTagHelper(ICompositeViewEngine viewEngine, IViewBufferScope viewBufferScope) : base(viewEngine, viewBufferScope)
    { }

    public ModelExpression Property { get; set; }

    public string LabelText { get; set; } = null;

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        this.For = Property;
        this.Name = "_FormGroup";
        // Have to use Viewdata to pass information to the partial view, because the model is the single property of the entity that will be posted back to the controller
        this.ViewData["TH_LabelText"] = LabelText;
        this.ViewData["TH_DataTypeName"] = Property.Metadata.DataTypeName;
        await base.ProcessAsync(context, output);
    }
}

Here's the partial view _FormGroup.cshtml. This generates the markup for a single field on a form with the Bootstrap styles. It also attaches the "datepicker" class to the input tag if the field is a date. Notice how asp-for is populated with @Model so this same view can be bound to any property on any entity model

@model object

@{
    string LabelText = (string)@ViewData["TH_LabelText"];
    string DataTypeName = (string) ViewData["TH_DataTypeName"];
    bool IsDate = (DataTypeName == "Date");
}

<div class="form-group row">
    @if (LabelText != null)
    {
    <label asp-for="@Model" class="control-label col-md-4">@LabelText</label>
    }
    <div class="col-md-8">
        <input asp-for="@Model" class="form-control @( IsDate ? "datepicker" : "")"  />
        <span asp-validation-for="@Model" class="text-danger"></span>
    </div>
</div>

Now in the Create or Edit view, where the model is the business entity, you can create the code to edit a single property on the entity like this.

<my-form-group label-text="Result date" property="ResultDate" view-data="ViewData" ></my-form-group>

In this case the model is a class with a field called ResultDate defined like this:

    [DataType(DataType.Date)]
    public DateTime? ResultDate { get; set; }

Note that you must set the view-data attribute to pass the ViewData object into tag helper. It uses ViewData to pass information on to the partial view. This isn't ideal but I couldn't think of any other way to pass information through to the partial view. I prefixed the keys with "TH_" to prevent the tag helper overwriting any other values in Viewdata.

Ricky answered 3/10, 2019 at 8:52 Comment(1)
This doesn't work. @Model is null in the partial. The asp-for expressions in the partial sort of bind to the property in the sense that they bind the name attribute to the property name, and the validation will appear to work when posted to the server. However the label is not resolved from the property expression (which is why you're needing to use the ViewData) and none of the Javascript validation attributes is generated so the client side validation does not work.Bowknot

© 2022 - 2024 — McMap. All rights reserved.