Show generic type parameters ("<T>") on your blog with a Razor component
We start with a simple problem and a simple solution: it's annoying to type the HTML escape codes for '<' and '>', so make a simple Razor component that will output them for you.
But our simple Razor component doesn't work, so we have to take a detour through the innards of the ASP.NET Razor engine to understand how the it handles "insignificant" whitespace. We'll see that because the Razor engine actually works too well, we can usually rest easy, completely unaware of whitespace, except in this case.
Finally we'll see how to slightly modify our Razor component, and then make it short and easy to use with Razor component inheritance.
Let's get started!
A Simple Problem
When writing a C# technical blog, you might find yourself wanting to talk about generic type parameters (like the <T> in IEnumerable<T>). I know I did!
But representing the angle-brackets ('<' and '>') in HTML is tricky: because angle brackets specify tags in HTML markup (for example, like <div>), how can you use '<' and '>' as just text? The key is to "escape" those characters. As in, from within HTML, escape from HTML, in order to indicate that those characters are just text.
So if you want show a open angle-bracket ('<', or "less-than") or close angle-bracket ('>', or "greater-than") they have to be escaped.
To escape these characters, you simply write out their escape sequence in your HTML, and instead of seeing the escape sequence, you'll see the character:
- "<" becomes <
- ">" becomes >
But between you and me, typing out all these escape sequences in your HTML can get really tedious, and worse it's error-prone. If you make a typo like "&tl;" there no HTML error, but instead of < you get &tl. (Since "&tl" is perfectly valid text, even if it is a little strange looking.) Because there are no errors, it's up to you to notice the typo before posting; hopefully you do!
Wouldn't it be better if there was some way to skip the error-prone tedium altogether? Well if you are lucky enough to be working with Razor components in ASP.NET, you can offload this to a Razor component.
A Simple Razor Component
Specifically, we want a Razor component that can be given a type parameter name (like "T") and will produce the HTML for <T>.
Here is the markup for a GenericTypeParameter
component:
@* In GenericTypeParameter.razor file *@
<@(this.TypeName)>
@code{
/// <summary>
/// The generic type parameter name.
/// </summary>
[Parameter]
public string TypeName { get; set; }
}
You have some Razor markup (<@(this.TypeName)>) and some code.
The markup specifies the HTML will be produced, and the code allows you to set the TypeName value of a <GenericTypeParameter>
Razor component from another component in the Razor framework.
And it seems to work. If you have some simple HTML like:
@* In BlogPost.razor file *@
<p>
... like the <GenericTypeParameter TypeName="T"> in IEnumerable<GenericTypeParameter TypeName="T">
</p>
So what's the problem?
Displaying Code on Your Blog
When displaying code on your blog, you'll want to the put code inside a <code> tag inside a <pre> tag. The <code> tag is how you instruct the browser to format the text within it as code (instead of as regular text). And the <pre> tag ("pre" for "pre-formatted") is how you instruct the browser to keep all the whitespace (like tabs and spaces) in your HTML. Because browsers usually discard whitespace, you can tell the browser that some HTML is pre-formatted, so it should not mess-up your formatting be discarding all the tabs and spaces.
Pre-formatting is especially useful with code, since copy-pasted code is usually formatted with tabs and spaces exactly how you want it to appear in HTML. For example, if you want to show people a for-loop:
for (int i = 0; i < length; i++)
{
Console.WriteLine(i);
}
<code>
for (int i = 0; i < length; i++)
{
Console.WriteLine(i);
}
</code>
for(inti=0;i<length;i++){Console.WriteLine(i);}
<pre>
<code>
for (int i = 0; i < length; i++)
{
Console.WriteLine(i);
}
</code>
</pre>
Great! Use a <code> tag and <pre> tag around code. But if you try to use your <GenericTypeParameter>
component like this:
<pre>
<code>
IEnumerable<GenericTypeParameter TypeName="T"/> x = list.AsEnumerable();
</code>
</pre>
IEnumerable
<T>
x = list.AsEnumerable();
What the heck?!?
This extra whitespace happens, counter-intuitively, because as of .NET 5.0 the Razor component engine trims "insignificant"" whitespace. This is a good thing, since if HTML ignores whitespace, why waste time transmitting whitespace characters in the first place?
Really a lot of great work went into implementing the Razor engine, since this trimming behavior is built in by default, and governed by rules that generally make sense. For example, the Razor engine respects <pre> tags by making an excepton for any whitespace contained within them. As opposed to usual "insignificant" whitespace, any whitespace within a <pre> tag is not removed.
So again, what the heck?
The problem here with the <GenericTypeParameter>
component arises because the Razor engine's whitespace trimming works too well!
Because it works so well, you forget (or never even knew) that behind the scenes whitespace trimming is happening.
So when you accidentally generate some extra whitespacem you never realize it.
That is, unless you generate some extra whitespace inside a <pre> tag.
A Closer Look
Where is this extra whitespace?
Take another look at our <GenericTypeParameter>
component's markup and notice that it contains some blank lines around the actual HTML text:
...
<@(this.TypeName)>
...
What to do?
Maybe this means we need to avoid new lines in our GenericTypeParameter
markup?
For example:s
@* In the GenericTypeParameter.razor file: *@<@(this.TypeName)>@code{
...
}
Fortunately, no. We can get the Razor engine to re-engage its rules and strip out the insignificant whitespace by wrapping the text in a <span> element.
...
<span><@(this.TypeName)></span>
...
That way the whitespace in the markup before and after the span element becomes insignificant again. (But be careful, any new lines within the span element's markup will be significant, so all the text inside the span must go on one line.)
Here is our final Razor component:
@* In the GenericTypeParameter.razor file:*@
<span><@(this.TypeName)></span>
@code{
/// <summary>
/// The generic type parameter name.
/// </summary>
[Parameter]
public string TypeName { get; set; }
}
Which you can use as:
<pre>
<code>
IEnumerable<GenericTypeParameter TypeName="T"/> x = list.AsEnumerable();
</code>
</pre>
BONUS: Shorten to <T/> instead of <GenericTypeParameter TypeName="T" />
Writing out "<GenericTypeParameter TypeName="T"/>" sure is wordy compared the resulting "<T>" output.
This got me thinking, could you could alias the component name to something shorter?
What I mean is, I like the name "GenericTypeParameter
". That's what the component should actually be called.
But when I use it, I want to use it as something short and easy, like <T/>.
Maybe I could use a file-level alias?
For example, in a C# file you can have the code:
using T = D8S.R0001.GenericTypeParameter;
T
is recognized as the type D8S.R0001.GenericTypeParameter
.
Similarly in a Razor markup file, can you have the code:
@using T = D8S.R0001.GenericTypeParameter
<T>
.
By this I mean the component name is not available in intellisense, nor is it recognized as a component name.
Instead the tag is treated as literal HTML, i.e. an HTML "<T>
" tag appears in the output, but not on screen,
because "<T>
" tag is not recognized as an HTML tag by a browser like the "<div>
" or "<p>
" tags.
So no visible component is generated for it.
But what if you still want a shortened name?
Instead what you can do is create a new Razor component called "T", and then inherit from the <GenericTypeParameter>
component:
@* In the T.razor file:*@
@inherits GenericTypeParameter
However the markup will not appear, since Razor component inheritance only shares properties and methods from the base class.
Well, unless you use the BuildRenderTree() hack:
@* In the T.razor file:*@
@inherits GenericTypeParameter
@{
base.BuildRenderTree(__builder);
}
Now the markup from the base class will also be inherited.
This <T>
component can now be used like:
<pre>
<code>
IEnumerable<T TypeName="T"/> x = list.AsEnumerable();
</code>
</pre>
The final touch is to set a default type name of "T" (but still allow any type name if the caller chooses to specify one).
Add a code section to the markup for the <T>
component with a constructor that sets the property on the base <GenericTypeParameter>
component:
@* In the T.razor file:*@
@inherits GenericTypeParameter
@{
base.BuildRenderTree(__builder);
}
@code {
public T()
{
base.TypeName = "T";
}
}
Conclusion
Phew! What started off as an almost trivially simple problem, automatically outputing '<' and '>' characters for discussing generic type parameters in code on a blog, wound up requiring Razor component inheritance and an exploration of Razor engine innards, specifically its rules for removing insignificant whitespace and text.
We wanted to create a simple Razor component to write out the "< T >" markup for us.
And we wanted to refer to it by a short name like <T>
instead of a long name
,
<GenericTypeParameter>
It took ahwhile, but in the end we made it happen.
I hope you enjoyed, and see you next time!