2

I want to create a reusable Blazor component that allows the user to define the HTML header tag (<h1>, <h2>, etc.) via a parameter.

My goal is to render a header dynamically like this:

<HeaderWithTooltip HeaderText="Invite" HeaderSize="3" TooltipText="asd"/>

The Component looks like this:

<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
    <DynamicComponent Type="@_headerTagType" Parameters="@_headerTagParameters" />

    @if (!string.IsNullOrWhiteSpace(TooltipText))
    {
        <MudIcon Icon="help" Style="color: var(--title-color)">
            <MudTooltip Text="@TooltipText"/>
    </MudIcon>
    }
</MudStack>

@code {
    [Parameter] public required string HeaderText { get; set; }
    [Parameter] public int HeaderSize { get; set; } = 1;
    [Parameter] public string? TooltipText { get; set; }

    private Type? _headerTagType;
    private readonly Dictionary<string, object> _headerTagParameters = new();

    protected override void OnParametersSet()
    {
        var validHeaderSize = HeaderSize is >= 1 and <= 6 ? HeaderSize : 1;
    _headerTagType = Type.GetType($"Microsoft.AspNetCore.Components.Web.HtmlElement+h{validHeaderSize}, Microsoft.AspNetCore.Components.Web");

    _headerTagParameters["class"] = "page-title";
    _headerTagParameters["ChildContent"] = (RenderFragment)(builder => { builder.AddContent(0, HeaderText); });
    }
}

Which should result in the following HTML-Header: <h2 class="page-title">Invite</h2>

As you can see in my Code, when no HeaderSize is given, then the defaultvalue is used, which should result in an <h1>-Header

I tried to use Type.GetType to dynamically get the type for the HTML header tags (h1, h2, etc.) and pass it to the DynamicComponent.

I was expecting this to work and to render a different header tag depending on the provided HeaderSize parameter.

However, Type.GetType returns null.

Therefore, my component does not render any header tag, as the DynamicComponent's Type is null.

1
  • A simple @case size{ 1: <h1>.... , 2: <h2> ... would be a quick and dirty solution. Otherwise, simply generate the raw HTML string you want and emit it with a MarkupString Commented Aug 6 at 6:52

3 Answers 3

4

Note: the pattern HeaderSize is >= 1 and <= 6 ? HeaderSize : 1 is nice. The compiler itself validates the arguments and will fail if invalid arguments (eg is >= 7 and <= 6) are used with :

CS8518: An expression of type 'int' can never match the provided pattern.


There's no need for reflection or complex code, as the desired output is just static HTML. The complexity and performance cost isn't worth it in this particular case.

Using raw HTML

It's possible to emit raw HTML using the MarkupString class. This can be dangerous if the input isn't sanitized though.

From the documentation example:

@page "/markup-strings"

<PageTitle>Markup Strings</PageTitle>

<h1>Markup Strings Example</h1>

@((MarkupString)myMarkup)

@code {
    private string myMarkup =
        "<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}

In your case the code should probably be :

<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
    @((MarkupString)stack_Header)

    @if (!string.IsNullOrWhiteSpace(TooltipText))
    {
        <MudIcon Icon="help" Style="color: var(--title-color)">
            <MudTooltip Text="@TooltipText"/>
    </MudIcon>
    }
</MudStack>

@code {
    [Parameter] public required string HeaderText { get; set; }
    [Parameter] public int HeaderSize { get; set; } = 1;
    [Parameter] public string? TooltipText { get; set; }

    private string stack_Header="";

    protected override void OnParametersSet()
    {
        var validHeaderSize = HeaderSize is >= 1 and <= 6 ? HeaderSize : 1;
        stack_Header=$"""<h{validHeaderSize{ class="page-title">{HeaderText}</h{validHeaderSize>""";
    }
}

Using Razor Templates

The next section in the docs shows how to use Razor Templates. Templates don't suffer from injection like Raw HTML does.

You could create one template per header in an array and select the appropriate one based on the size. :

<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
    @headers[header_index];

    ...
</MudStack>

@code {
    [Parameter] public required string HeaderText { get; set; }
    [Parameter] public int HeaderSize { get; set; } = 1;
    [Parameter] public string? TooltipText { get; set; }


    RenderFragment[] headers;
    int header_index=1;

    protected override void OnInitialized()
    {
        headers=new RenderFragment[6];
        headers[0]=@<h1 class="page-title">@HeaderText</h1>;
        headers[1]=@<h2 class="page-title">@HeaderText</h2>;
        headers[2]=@<h3 class="page-title">@HeaderText</h3>;
        headers[3]=@<h4 class="page-title">@HeaderText</h4>;
        headers[4]=@<h5 class="page-title">@HeaderText</h5>;
        headers[5]=@<h6 class="page-title">@HeaderText</h6>;
    }
    protected override void OnParametersSet()
    {
        header_index = HeaderSize is >= 1 and <= 6 ? HeaderSize-1 : 0;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks very much, but I have to add, that the following line of code needs this backslashes: stack_Header=$"<h{validHeaderSize{ class=\"page-title\">{HeaderText}</h{validHeaderSize>";
Oops, forgot about this. No need for backslashes though when there are raw strings
2

You could create RenderFragment dynamically instead of creating all possible cases. Below is sample component definition:

@HeaderTag

@code {
    [Parameter] public required string HeaderText { get; set; }
    [Parameter] public int HeaderSize { get; set; } = 1;

    private RenderFragment HeaderTag => builder =>
    {
        var validSize = Math.Clamp(HeaderSize, 1, 6);
        var tag = $"h{validSize}";

        builder.OpenElement(0, tag);
        builder.AddAttribute(1, "class", "page-title");
        builder.AddContent(2, HeaderText);
        builder.CloseElement();
    };
}

5 Comments

Yes. Note that you could replace string HeaderText with RenderFragment ChildContent when required.
That's a good option of there aren't so few and simple cases. The tags are essentially static and the OP's real problem right now seems to be overcomplicating things. Math.Clamp is worse than the HeaderSize is >= 1 and <= 6 pattern too. The compiler itself will fail if HeaderSize is >= 7 and <=6 is used, while Clamp will throw at runtime
@PanagiotisKanavos I don't see the problem with Clamp(). That exception would happen only once, in developer time.
But the pattern will cause a CS8518 compilation error at compile time. Even if you make a change and forget to test it, the compiler will catch it. I hadn't thought of doing what the OP did until I tried to see the difference between the seemingly identical Clamp, ?: or pattern, and realized the pattern is better
I can't imagine this escaping even the barest minimum of testing. And I don't like >= 7 becoming 1, but that's a requirement issue.
0

You can directly apply the html header tags is blazor component like

<h1>hello, buddy</h1>

I hope it will help you.

1 Comment

But now make that 1 in h1 dynamic

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.