ASP .Net Core Custom Tag Helper to Convert CamelCase Properties to spaces
Asked Answered
D

3

5

Is it possible in ASP.Net Core to automatically convert camel case property names in view models to insert spaces into the corresponding labels when using tag helpers?

If my view model looks like this...

[Display(Name = "First Name")]
public string FirstName { get; set; }

[Display(Name = "Last Name")]
public string LastName { get; set; }

[Display(Name = "Referral Date")]
public DateTime ReferralDate { get; set; }

It seems like a lot of extra configuration applying data annotations such as

[Display(Name = "First Name")]

to simply insert a space between words. It would make sense that Tag Helpers would insert the space by default to avoid this manual configuration and potential typos.

If not could a custom tag helper assist in this situation and if so how would it work?

Doorknob answered 20/3, 2017 at 13:58 Comment(0)
T
3

You can achieve this by building and registering a custom display metadata provider. There are libraries that will perform more elaborate "humanization" to the property names, but you can achieve something pretty useful with some trusty regular expressions.

public class HumanizerMetadataProvider : IDisplayMetadataProvider
{
    public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
    {
        var propertyAttributes = context.Attributes;
        var modelMetadata = context.DisplayMetadata;
        var propertyName = context.Key.Name;

        if (IsTransformRequired(propertyName, modelMetadata, propertyAttributes))
        {
            modelMetadata.DisplayName = () => SplitCamelCase(propertyName);
        }
    }

    private static string SplitCamelCase(string str)
    {
        return Regex.Replace(
            Regex.Replace(
                str,
                @"(\P{Ll})(\P{Ll}\p{Ll})",
                "$1 $2"
            ),
            @"(\p{Ll})(\P{Ll})",
            "$1 $2"
        );
    }

    private static bool IsTransformRequired(string propertyName, DisplayMetadata modelMetadata, IReadOnlyList<object> propertyAttributes)
    {
        if (!string.IsNullOrEmpty(modelMetadata.SimpleDisplayProperty))
            return false;

        if (propertyAttributes.OfType<DisplayNameAttribute>().Any())
            return false;

        if (propertyAttributes.OfType<DisplayAttribute>().Any())
            return false;

        if (string.IsNullOrEmpty(propertyName))
            return false;

        return true;
    }
}

The IsTransformRequired method ensures that you can still override the provider with a decorated property when you need to.

Register the provider on startup through the AddMvcOptions method on IMvcBuilder:

builder.AddMvcOptions(m => m.ModelMetadataDetailsProviders.Add(new HumanizerMetadataProvider()));
Trebuchet answered 20/3, 2017 at 14:38 Comment(5)
this works a treat but lead me to this link for full explanation and also I needed to install this nuget package linkDoorknob
@ojm Thanks for the Humanizer link. Just what I've been looking for.Citronella
How do you configure this in an ASP.NET Core 3.0 application?Constitutionality
I tried your code but it didn't seem to take effect when I tried it on a Razor PageConstitutionality
Also didn't work when I tried it via a standard controller and action?Constitutionality
C
4

If you only care about label, you can easily override LabelTagHelper.

[HtmlTargetElement("label", Attributes = "title-case-for")]
public class TitleCaseTagHelper : LabelTagHelper
{
    public TitleCaseTagHelper(IHtmlGenerator generator) : base(generator)
    {
    }

    [HtmlAttributeName("title-case-for")]
    public new ModelExpression For { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        if (output == null)
            throw new ArgumentNullException("output");

        string name = For.ModelExplorer.Metadata.DisplayName ?? For.ModelExplorer.Metadata.PropertyName;
        name = name.Humanize(LetterCasing.Title);
        TagBuilder tagBuilder = this.Generator.GenerateLabel(
            this.ViewContext,
            this.For.ModelExplorer,
            this.For.Name,
            name,
            (object) null);
        if (tagBuilder == null)
            return;
        output.MergeAttributes(tagBuilder);
        if (output.IsContentModified)
            return;
        TagHelperContent childContentAsync = await output.GetChildContentAsync();
        if (childContentAsync.IsEmptyOrWhiteSpace)
            output.Content.SetHtmlContent((IHtmlContent) tagBuilder.InnerHtml);
        else
            output.Content.SetHtmlContent((IHtmlContent) childContentAsync);
    }
}

Usage

<label title-case-for="RememberMe" class="col-md-2 control-label"></label>

Please ensure to place using statement and @addTagHelper inside _ViewImports.cshtml.

@using YourNameSpace.Helpers
@addTagHelper *, YourNameSpace

Note

I use Humanizer English Only NuGet Package - Humanizer.Core. It is more robust than writing my own method. If you doesn't like overhead, you can just use Regular Expression.

Candide answered 20/3, 2017 at 15:33 Comment(0)
T
3

You can achieve this by building and registering a custom display metadata provider. There are libraries that will perform more elaborate "humanization" to the property names, but you can achieve something pretty useful with some trusty regular expressions.

public class HumanizerMetadataProvider : IDisplayMetadataProvider
{
    public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
    {
        var propertyAttributes = context.Attributes;
        var modelMetadata = context.DisplayMetadata;
        var propertyName = context.Key.Name;

        if (IsTransformRequired(propertyName, modelMetadata, propertyAttributes))
        {
            modelMetadata.DisplayName = () => SplitCamelCase(propertyName);
        }
    }

    private static string SplitCamelCase(string str)
    {
        return Regex.Replace(
            Regex.Replace(
                str,
                @"(\P{Ll})(\P{Ll}\p{Ll})",
                "$1 $2"
            ),
            @"(\p{Ll})(\P{Ll})",
            "$1 $2"
        );
    }

    private static bool IsTransformRequired(string propertyName, DisplayMetadata modelMetadata, IReadOnlyList<object> propertyAttributes)
    {
        if (!string.IsNullOrEmpty(modelMetadata.SimpleDisplayProperty))
            return false;

        if (propertyAttributes.OfType<DisplayNameAttribute>().Any())
            return false;

        if (propertyAttributes.OfType<DisplayAttribute>().Any())
            return false;

        if (string.IsNullOrEmpty(propertyName))
            return false;

        return true;
    }
}

The IsTransformRequired method ensures that you can still override the provider with a decorated property when you need to.

Register the provider on startup through the AddMvcOptions method on IMvcBuilder:

builder.AddMvcOptions(m => m.ModelMetadataDetailsProviders.Add(new HumanizerMetadataProvider()));
Trebuchet answered 20/3, 2017 at 14:38 Comment(5)
this works a treat but lead me to this link for full explanation and also I needed to install this nuget package linkDoorknob
@ojm Thanks for the Humanizer link. Just what I've been looking for.Citronella
How do you configure this in an ASP.NET Core 3.0 application?Constitutionality
I tried your code but it didn't seem to take effect when I tried it on a Razor PageConstitutionality
Also didn't work when I tried it via a standard controller and action?Constitutionality
M
1

how about using a custom attribute and overriding DisplayNameAttribute

 public class DisplayWithSpace : DisplayNameAttribute
    {
        public DisplayWithSpace([System.Runtime.CompilerServices.CallerMemberName]  string memberName ="")
        {

            Regex r = new Regex(@"(?!^)(?=[A-Z])");
            DisplayNameValue = r.Replace(memberName, " ");
        }
    }

and your property will be

[DisplayWithSpace]

public string FatherName { get; set; }
Marchak answered 20/3, 2017 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.