简体   繁体   中英

Accessing custom attributes from model metadata in a tag helper

I am working on a new project in .net Core having previously been working with .net Framework.

I wish to produce html select elements for boolean properties but using custom values instead of True and False (mainly "Yes" and "No"). In previous projects I have used the following method:

Create a custom attribute for boolean values:

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class BooleanCustomValuesAttribute : Attribute, IMetadataAware
{
    public string TrueValue { get; set; }
    public string FalseValue { get; set; }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues["TrueValue"] = TrueValue;
        metadata.AdditionalValues["FalseValue"] = FalseValue;
    }
}

Use this in my model:

[BooleanCustomValues(TrueValue = "Yes", FalseValue = "No")]
public bool ThisWorks { get; set; }

Then in an editor template I can access these values for adding to a select:

object trueTitle;
ViewData.ModelMetadata.AdditionalValues.TryGetValue("TrueValue", out trueTitle);
trueTitle = trueTitle ?? "True";

object falseTitle;
ViewData.ModelMetadata.AdditionalValues.TryGetValue("FalseValue", out falseTitle);
falseTitle = falseTitle ?? "False";

I am now trying to do something similar in .net core, this time using a tag-helper instead of an editor template.

I understand that setting the AdditionalValues as above is not supported in core?

The closest I have found is this answer: Getting property attributes in TagHelpers

My tag helper code looks like this currenty (outputting p just for testing):

public class BooleanSelectTagHelper: TagHelper
    {
        [HtmlAttributeName("asp-for")]
        public ModelExpression Source { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "p";
            output.TagMode = TagMode.StartTagAndEndTag;

            var booleanAttribute = Source.Metadata.ContainerType.GetProperty(Source.Name).GetCustomAttribute(typeof(BooleanCustomValuesAttribute));

            var contents = $@"{booleanAttribute.TrueValue} or {booleanAttribute.FalseValue}";

            output.Content.SetHtmlContent(new HtmlString(contents));
        }
    }

However I can't get it to work as Source.Metadata.ContainerType.GetProperty(Source.Name).GetCustomAttributes() returns null.

Anoyingly when debugging and looking at Source.Metadata in a watch window I can see exactly what I want to access under Source.Metadata.Attributes - however this is not something that seems to be exposed outside the debugger.

Apologies for length of post - any pointers would be much appreciated (including tellimg me I am doing this all wrong!)

You are missing HtmlTargetElement attribute on your tag helper:

[HtmlTargetElement(Attributes = "asp-for")] // <== Add this
public class BooleanSelectTagHelper: TagHelper
{
    [HtmlAttributeName("asp-for")]
    public ModelExpression Source { get; set; }
}

Also, don't forget to import your own tag helpers in _ViewImports.cshtml file after Microsoft's tag helpers. Replace the "WebApp" with the name of your assembly containing the tag helpers to add. Read more in official documentation .

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WebApp

Maybe this will help, this is what I use to add the attributes for unobtrusive validation when I'm creating custom TagHelpers. You can just adapt this to whatever attribute you want to use. From my experience, when you have the public member for ModelExpression, just call it AspFor, if you do not, it does not seem to get any of the attributes. But if the name matches, then I never have an issue. Im currently using this on .NET5.

public class BooleanSelectTagHelper: TagHelper {
            
    public ModelExpression AspFor { get; set; }
    
    public override void Process(TagHelperContext context, TagHelperOutput output){
        //.. setup excluded for example
         var attribute = this.AspFor
        .Metadata
        .ContainerType
        .GetProperty(AspFor.Name)
        .GetCustomAttributes(typeof(RequiredAttribute), false)                
        .FirstOrDefault();
        if(attribute != null && attribute is RequiredAttribute requiredAttribute) {
            output.MergeAttribute("data-val", "true");
            output.MergeAttribute("data-val-required", requiredAttribute.FormatErrorMessage(AspFor.Name));                
        }
        //.. code to display excluded for example
    }
}

I usually attempt to get the attribute, then do a null check and use an "as" statement to cast right inside of the if statement. Then access whatever you need to inside of the if statement.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM