Custom Validation TagHelper: Modify Child Element

147 Views Asked by At

I am trying to implement server side validation, by changing the class of an HTML child element within a TagHelper. The TagHelper is the "kendo-datepicker", however the current code modifies the class of the "span" tag and I would like to modify the class of the child "input" tag.

After some research it appears GetChildContentAsync may be useful, but all my attempts to use it are failing.

Taghelper:

[HtmlTargetElement("kendo-datepicker")]
[HtmlTargetElement("select")]
[HtmlTargetElement("input")]
public class ValidationErrorClassTagHelper : TagHelper
{
    [HtmlAttributeNotBound]
    [ViewContext]
    public ViewContext ViewContext { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {

        //output.Attributes.ContainsName("class")

        if (context.TagName == "kendo-datepicker")
        {
            TagHelperAttribute taghelperAttribute = context.AllAttributes["for"];
            if (taghelperAttribute != null)
            {
           
                ModelExpression modelExpression = (ModelExpression)taghelperAttribute.Value;

                ViewContext.ViewData.ModelState.TryGetValue(modelExpression.Name, out ModelStateEntry entry);

                if (entry != null && entry.Errors.Any())
                {
                    output.Attributes.SetAttribute("class", "form-control " + "is-invalid");
                 
                }
            }
        }

    }
}

Current (incorrect) HTML Output:

<span class="k-datepicker k-input form-control is-invalid k-input-solid k-input-md k-rounded-md" style=""><input class="form-control k-input-inner valid" required="" data-val="true" data-val-required="Enter the date observed." id="ADR_Date" name="ADR.Date" type="text" value="6/07/2023" data-role="datepicker" role="combobox" aria-expanded="false" aria-haspopup="grid" aria-controls="ADR_Date_dateview" autocomplete="off" aria-disabled="false" aria-readonly="false" aria-describedby="ADR_Date-error" aria-invalid="false"><button aria-label="select" tabindex="-1" class="k-input-button k-button k-icon-button k-button-md k-button-solid k-button-solid-base" type="button" role="button"><span class="k-icon k-i-calendar k-button-icon"></span></button></span>

Unfortunately the taghelperoutput is rendering jquery instead of html:

PostElement = <script>kendo.syncReady(function(){jQuery("#ADR_Date").kendoDatePicker({"format":"d","value":new Date(2023,6,6,0,0,0,0)});});</script>
3

There are 3 best solutions below

0
Lajos Arpad On

A very raw idea would be to do

TagHelperContent innerContent = await output.GetChildContentAsync();

and then get the content via GetContent as a string and then parse this string and then SetContent after you added the class. This is an admittedly dirty solution we should only resort to if there is nothing else that we can do.

Alternatively, you could look into context.Items and see whether the input is there under some key. If so, then you could work with that one.

1
Chuck Terry On

I think the simplest way would be:

if (entry != null && entry.Errors.Any()) {
  // Asynchronously get string containing child content
  string childContent = (await output.GetChildContentAsync()).GetContent();
  // Insert your classes by matching the opening input tag along with the class attribute and replacing it.
  string modifiedContent = Regex.Replace(
    childContent,
    Regex.Escape("<input class=\""),
    "<input class=\"form-control is-invalid ");
  // Update the output
  output.Content.SetHtmlContent(modifiedContent);
}

Note: if your HTML structure ever changes to include a second input, this will require updating.

String manipulation isn't exactly the preferred method for doing this, but without additional context about your issue, it's the most straightforward. Depending on your environment, SetHtmlContent() may cause problems. If that's the case, remove both leading < in the Regex and update SetHtmlContent() to SetContent(). I only included it for the extremely minor performance boost of skipping a call to HTMLEncoder.Encode().

1
Ramon Soarez On

You can modify the "input" child element inside the "kendo-datepicker" Tag Helper, using GetChildContentAsync() as follows:

if (context.TagName == "kendo-datepicker")
    {
        TagHelperAttribute taghelperAttribute = context.AllAttributes["for"];
        if (taghelperAttribute != null)
        {
            ModelExpression modelExpression = (ModelExpression)taghelperAttribute.Value;

            ViewContext.ViewData.ModelState.TryGetValue(modelExpression.Name, out ModelStateEntry entry);

            if (entry != null && entry.Errors.Any())
            {
              
                var childContent = await output.GetChildContentAsync();
                var inputElement = childContent.FindDescendants("input").FirstOrDefault();
                if (inputElement != null)
                {
                    inputElement.Attributes.Add("class", "form-control is-invalid");
                }
            }
        }
    }
    

In this code, the childContent.FindDescendants("input") method is used to find and retrieve all descendant elements within the childContent that match the given tag name "input" and FirstOrDefault() is a method used to get the first element from the collection of matching elements returned by .FindDescendants("input").

Give it a try and let us know if you managed to solve your case correctly.