HTML Gen can be considered a framework for creating static sites using C# using the dotnet runtime.
It provides a way of creating html pages, page layouts and components in a declarative manner.
Use this for testing or inspiration, not in a production environment!
When using this in a dotnet app, register the core services using the extension method .AddHtmlGen()
and then in the middleware pipeline using .UseHtmlGen()
Minimal API example
var builder = WebApplication.CreateBuilder(args);
builder.AddHtmlGen();
//Code omitted
var app = builder.Build();
app.UseHtmlGen();
Pages will be the bread and butter of your site. To be functional, page must do two things:
- Use the Class Attribute
[Route(string)]
- Inherit from Abstract Class
Page
From there, your page will be reachable at the value provided in the Route Attribute.
[Route("/")]
public class Home : Page
{
public override async Task<MarkupNode> RenderContent() =>
div("Hello world!");
}
They are two types of layout you can use.
The MainLayout is the page outside of the <body>
tag.
If you dont declare a main layout, default values will be used, which looks like this
<!DOCTYPE html>
<html lang="en">
<head>
<title>HTMLGen C# - Static web-site framework</title>
<meta charset="UTF-8">
<meta viewport="width=device-width, initial-scale=1.0">
</head>
<body>
<!-- layouts and pages rendered in here -->
</body>
</html>
By inheriting from MainLayoutBase
, you can customize the tags in the <head>
.
public class MainLayout : MainLayoutBase
{
public MainLayout()
{
UseHyperscript = true;
UseTailwind = true;
}
protected override MarkupNode RenderHeadTags() =>
Fragment(
meta("description", "Framework for creating static sites"),
meta("keywords", "C#, HTML, CSS, dotnet"),
base.RenderMetaTags()
);
protected override async Task<MarkupNode> RenderLayout() => await RenderPageContent();
}
By overriding the RenderHeadTags
function, you can provide additional tags that go in the head.
The base.RenderHeadTags()
function contain the charset and responsive-viewport meta tags, so they're useful to include.
Overriding RenderLayout
is required as this is the entrypoint for the layout. If you don't wish to return any further inner content, you can just await the call to RenderPageContent()
Calling RenderPageContent()
is where all the requested page's content will be rendered
protected override async Task<MarkupNode> RenderLayout() =>
Fragment(
header(
div("Content for the header on every page")
),
main(
await RenderPageContent()
)
);
MainLayoutBase
contains some other properties which you can configure to further customize the main layout.
bool NormalizeBaseCss
is a flag on whether to include a stylesheet which normalizes the base stylesheet for the browser. This includes setting global box sizing, and removing default user-agent margins.
bool UseTailwind
is a flag for on whether to include a<link>
tag in the head providing a source to TailwindCSS's global stylesheet.
bool UseHyperscript
is a flag for on whether to include a<link>
tag in the head providing the ability to use Hyperscript tags on elements.
string DocumentLanguage
value for the<html>
attribute lang
The other type is a layout which can be used across pages to establish a common style, ie authenticated page, unauthenticated page, etc.
Creating one is similar to a MainLayout
, in that you include the overridden method RenderLayout()
, and then call RenderPageContent
to render the page's content, except you inherit from Layout
.
public class BodyLayout : Layout
{
protected override async Task<MarkupNode> RenderLayout() =>
main(
await RenderPageContent()
).WithClasses("w-full h-screen flex flex-col bg-slate-900/80");
}
And then to use it in a Page
, you apply the [Layout]
Attribute to the Page
Class.
[Route("/")]
[Layout(typeof(BodyLayout))]
public class Home : Page
{
public override async Task<MarkupNode> RenderContent() =>
div("Hello world!");
}
Components are reusable sections of declared elements that can be used elsewhere in pages and layouts so you don't have to repeat yourself, and provide some method of composition to the framework.
You can create a component by inheriting from the Component
Abstract Class, and then override the RenderComponent()
method with your desired markup.
public class Header : Component
{
public override MarkupNode RenderComponent() =>
header(
div("This is the header")
).WithClasses("h-32", "bg-zinc-700");
}
You can then use it in your pages and layouts by calling the Component
helper and providing the Type Parameter of the component
public class BodyLayout : Layout
{
protected override async Task<MarkupNode> RenderLayout() =>
main(
Component<Header>(),
await RenderPageContent()
).WithClasses("w-full h-screen flex flex-col bg-slate-900/80");
}