Markupolation = Markup + String Interpolation
- Installation
- Introduction
- When should I use Markupolation?
- Markupolation
- Markupolation.Extensions
- Using directives
- Performance
- Samples
Install via NuGet:
PM> Install-Package Markupolation
Enable ImplicitUsings
in the .csproj
file:
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
📦 Markupolation
is a library for HTML templating in C# with a fluent API.
An expression like this:
DOCTYPE() + html(head(e.title("Markupolation")), body(h1("Hello, World!")))
generates the following result:
<!DOCTYPE html><html><head><title>Markupolation</title></head><body><h1>Hello, World!</h1></body></html>
📦 Markupolation.Extensions
adds extension methods to control flow.
Expressions like these:
var links = new[] { new { Url = "#", Title = "Foo", Active = true }, new { Url = "#", Title = "Bar", Active = false } };
links.Each((x, index) => a(href(x.Url), id($"link{index}"), x.IfMatch(x => x.Active, x => class_("active")), x.Title));
generates the following result:
<a href="#" id="link0" class="active">Foo</a><a href="#" id="link1">Bar</a>
In some situations, you don't have access or don't want to use a template/view engine like Razor.
Maybe you are building
- a Minimal API, or
- Azure Functions
to deliver
HTML
Over The Wire- instead of
JSON
to
- a Blazor WebAssembly app, or
- an Azure Static Web App site with
Hotwire
orHTMX
?
In cases like these, Markupolation
could be a good fit to generate HTML for you.
Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire. This makes for fast first-load pages, keeps template rendering on the server, and allows for a simpler, more productive development experience in any programming language, without sacrificing any of the speed or responsiveness associated with a traditional single-page application.
-- hotwired.dev
htmx is a library that allows you to access modern browser features directly from HTML, rather than using javascript.
Note that when you are using htmx, on the server side you typically respond with HTML, not JSON.
htmx expects responses to the AJAX requests it makes to be HTML, typically HTML fragments.
htmx will then swap the returned HTML into the document at the target specified and with the swap strategy specified.
-- htmx.org
Interpolate strings with HTML elements and attributes:
$@"{DOCTYPE() +
html(lang("en"),
head(
meta(charset("utf-8")),
e.title("Markupolation"),
meta(name("description"), content("Sample of how to use Markupolation")),
meta(name("viewport"), content("width=device-width, initial-scale=1"))
),
body(
h1("Hello, World!"),
p("This is ", mark(a.title("Markup with string interpolation"), "Markupolation"), " in action.")
)
)}";
Result (formatted):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Markupolation</title>
<meta name="description" content="Sample of how to use Markupolation" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h1>Hello, World!</h1>
<p>This is <mark title="Markup with string interpolation">Markupolation</mark> in action.</p>
</body>
</html>
ℹ️ The names of element, attribute and event handler content attribute methods are in lowercase to reflect the HTML specification.
ℹ️ Names that conflict with the C# keywords are suffixed with a _
.
Elements:
base_
object_
Attributes:
as_
checked_
class_
default_
for_
is_
readonly_
ℹ️ Attributes that contain hyphen (-
) are converted to snake_case:
accept_charset
http_equiv
abbr
cite
data
form
label
slot
span
style
title
Use the predefined aliases as shorthands for the methods:
e.title("Title element")
a.title("Title attribute")
ℹ️ Custom elements that are not available in the API can be created with the Element
class:
new Element("""
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
""")
Or via the E
alias:
new E("""
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
""")
ℹ️ Custom attributes that are not available in the API can be created with the Attribute
class:
new Attribute("property", "og:title")
Or via the A
alias:
new A("property", "og:title")
📦 Available via NuGet as a separate package: Markupolation.Extensions
Use lambda expressions to control flow:
Func<int, bool> fizz = (int i) => i % 3 == 0;
Func<int, bool> buzz = (int i) => i % 5 == 0;
var numbers = Enumerable.Range(1, 15);
$@"{DOCTYPE() +
html(lang("en"),
head(
meta(charset("utf-8")),
e.title("Markupolation.Extensions"),
meta(name("description"), content("Sample of how to use Markupolation.Extensions")),
meta(name("viewport"), content("width=device-width, initial-scale=1"))
),
body(
ul(
numbers.Each(x => li(
x.IfMatch(i => fizz(i) && buzz(i), i => strong("FizzBuzz")),
x.IfMatch(i => fizz(i) && !buzz(i), i => em("Fizz")),
x.IfMatch(i => !fizz(i) && buzz(i), i => em("Buzz")),
x.IfMatch(i => !fizz(i) && !buzz(i), i => i.ToString())
))
)
)
)}";
Result (formatted):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Markupolation.Extensions</title>
<meta name="description" content="Sample of how to use Markupolation.Extensions" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li><em>Fizz</em></li>
<li>4</li>
<li><em>Buzz</em></li>
<li><em>Fizz</em></li>
<li>7</li>
<li>8</li>
<li><em>Fizz</em></li>
<li><em>Buzz</em></li>
<li>11</li>
<li><em>Fizz</em></li>
<li>13</li>
<li>14</li>
<li><strong>FizzBuzz</strong></li>
</ul>
</body>
</html>
Looping on IEnumerable<T>
:
Each<T>
Conditionals on IEnumerable<T>
:
IfEmpty<T>
IfNotEmpty<T>
Conditionals on T
:
IfNull<T>
IfNotNull<T>
IfMatch<T>
Conditionals on string
:
IfNullOrEmpty
IfNotNullOrEmpty
Conditionals on T?
:
IfHasValue<T>
When ImplicitUsings
are enabled:
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
These using directives are applied automatically:
<ItemGroup>
<Using Include="Markupolation" />
<Using Include="Markupolation.Elements" Static="True" />
<Using Include="Markupolation.Elements" Alias="e" />
<Using Include="Markupolation.Element" Alias="E" />
<Using Include="Markupolation.Attributes" Static="True" />
<Using Include="Markupolation.Attributes" Alias="a" />
<Using Include="Markupolation.Attribute" Alias="A" />
<Using Include="Markupolation.EventHandlerContentAttributes" Static="True" />
</ItemGroup>
If you do not want to enable ImplicitUsings
in your project, you can copy and paste the above into your .csproj
file.
Alternatively you can add the following using directives in a .cs
file:
global using Markupolation;
global using static Markupolation.Elements;
global using e = Markupolation.Elements;
global using E = Markupolation.Element;
global using static Markupolation.Attributes;
global using a = Markupolation.Attributes;
global using A = Markupolation.Attribute;
global using static Markupolation.EventHandlerContentAttributes;
String concatenation in C# can be done in different ways.
Conventional wisdom is to use the StringBuilder
class.
Jon Skeet offers advice on Concatenating Strings Efficiently.
C# 10 offers
improved
and
constant
interpolated strings that you can benefit from when using Markupolation
.
The tests folder contains some benchmarks.
The samples folder contains examples with:
- Minimal API
- Azure Functions
- Blazor WebAssembly and HTML Over The Wire
- HTMX and HTML Over The Wire