AngryMonkey.CloudWeb
2.3.0
dotnet add package AngryMonkey.CloudWeb --version 2.3.0
NuGet\Install-Package AngryMonkey.CloudWeb -Version 2.3.0
<PackageReference Include="AngryMonkey.CloudWeb" Version="2.3.0" />
<PackageVersion Include="AngryMonkey.CloudWeb" Version="2.3.0" />
<PackageReference Include="AngryMonkey.CloudWeb" />
paket add AngryMonkey.CloudWeb --version 2.3.0
#r "nuget: AngryMonkey.CloudWeb, 2.3.0"
#:package AngryMonkey.CloudWeb@2.3.0
#addin nuget:?package=AngryMonkey.CloudWeb&version=2.3.0
#tool nuget:?package=AngryMonkey.CloudWeb&version=2.3.0
CloudWeb
CloudWeb is an ASP.NET Core library for managing page metadata and static asset bundles in Blazor and MVC applications.
Demo
https://angrymonkeycloud.github.io/CloudWeb/
Features
- Fluent per-page metadata (
title,description,keywords,favicon,theme-color, web app manifest) - Global defaults through
CloudWebConfigwith per-page overrides - Title prefix/suffix, add-ons, and automatic 64-character length limiting
- Description auto-truncation at 160 characters
- Robots directives (
noindex,nofollow) and automatic staging protection - CSS/JS bundle injection with minified path insertion and cache-busting versioning
- Built-in CDN features (
Maps,TextEditor,JQuery) and a legacy exports shim - Crawler detection using an extensive built-in user-agent list
- Native support for both Blazor and MVC
Installation
dotnet add package AngryMonkey.CloudWeb
Quick Start
1. Register the library
builder.Services.AddCloudWeb(config =>
{
config.TitleSuffix = " - My Application";
config.PageDefaults
.SetTitle("Home")
.SetDescription("Default site description.")
.SetKeywords("cloudweb, aspnetcore, seo")
.SetFavicon("/favicon.svg")
.SetThemeColor("#0B5FFF")
.SetManifest("/site.webmanifest");
});
2. Blazor integration
App.razor — add a SectionPlaceholder inside <head>:
<head>
<SectionPlaceholder SectionName="CloudWeb" />
</head>
Routes.razor — render the managed head content:
<CloudHeadContent />
Per-page usage:
@inject CloudPage CloudPage
@code {
protected override void OnInitialized()
{
CloudPage
.SetTitle("Dashboard")
.SetDescription("Operational dashboard")
.SetKeywords("dashboard, analytics");
}
}
3. MVC integration
Inherit from CloudController and call CloudPage() in each action:
public class HomeController(CloudPage cloudPage) : CloudController(cloudPage)
{
public IActionResult Index()
{
CloudPage("Home").SetDescription("Home page");
return View();
}
}
Layout (_Layout.cshtml) — render the head component:
<head>
<component type="typeof(CloudHeadInit)" render-mode="Static" />
</head>
Page Metadata
CloudPage exposes a fluent API for setting all <head> metadata. All methods return this and fire the OnModified event, which triggers a Blazor StateHasChanged().
@inject CloudPage CloudPage
@code {
protected override void OnInitialized()
{
CloudPage
.SetTitle("Contact")
.SetDescription("Get in touch with us.")
.SetKeywords("contact, support")
.SetFavicon("/icons/contact.svg")
.SetThemeColor("#0B5FFF")
.SetManifest("/site.webmanifest");
}
}
Title
SetTitle(string) sets the page title. CloudWebConfig.TitlePrefix and TitleSuffix are automatically prepended/appended:
// Config: TitleSuffix = " - My App"
CloudPage.SetTitle("About");
// Renders: <title>About - My App</title>
When no title is set on the page, the global PageDefaults.Title is used instead (without prefix/suffix).
Title add-ons
Additional tokens can be appended to the title. The combined title is limited to 64 characters — tokens that would exceed the limit are silently dropped:
CloudPage.SetTitleAddOns(["Page 3", "Category A"]);
Description
SetDescription(string) sets the <meta name="description"> tag. Descriptions longer than 160 characters are automatically truncated with ....
Keywords
SetKeywords(string) sets the <meta name="keywords"> tag.
Favicon
SetFavicon(string) sets the <link rel="icon"> href. CloudWeb automatically emits the matching MIME type for common icon formats and allows a global default to be overridden per page.
Browser theme and web app manifest
SetThemeColor(string) sets the browser UI theme color, while SetManifest(string) adds the optional web app manifest link. Both can be configured globally through PageDefaults or overridden per page.
Global defaults
Per-page values override the global defaults using null-coalescing — if a page explicitly sets a value it wins; otherwise the global default from PageDefaults applies.
builder.Services.AddCloudWeb(config =>
{
config.TitlePrefix = "";
config.TitleSuffix = " - My App";
config.PageDefaults
.SetFavicon("/favicon.svg")
.SetThemeColor("#0B5FFF")
.SetManifest("/site.webmanifest")
.SetDescription("Site-wide default description.")
.SetKeywords("default, keywords");
});
Metadata API reference
| Method | Description |
|---|---|
SetTitle(string) |
Sets the page title. Prefix/suffix are applied automatically. |
SetDescription(string) |
Sets the meta description. Values over 160 characters are truncated with an ellipsis. |
SetKeywords(string) |
Sets the meta keywords tag. |
SetFavicon(string) |
Sets the favicon href. Overrides the global default when provided. |
SetThemeColor(string) |
Sets the browser UI theme color. |
SetManifest(string) |
Sets the web app manifest href. |
SetTitleAddOns(IEnumerable<string>) |
Appends additional tokens to the title, respecting the 64-character limit. |
Asset Bundles
CloudWeb manages CSS and JavaScript file injection into the <head>. Global bundles (from PageDefaults) are rendered before per-page bundles.
Appending bundles
// Simple string overload
CloudPage.AppendBundle("~/css/theme.css");
CloudPage.AppendBundle("~/js/app.js");
// Multiple at once
CloudPage.AppendBundles("~/css/a.css", "~/js/b.js");
// Full options
CloudPage.AppendBundle(new CloudBundle
{
Source = "~/js/analytics.js",
MinOnRelease = true, // inserts .min. before the extension in non-Development environments
AppendVersion = true, // appends ?v= cache-busting query string
Defer = true, // adds defer attribute to script tags
Async = false,
UseMapping = true, // resolve path through the static asset manifest
AddOns = null, // freeform attribute string appended verbatim to the tag
});
Inserting bundles at a specific position
CloudPage.InsertBundle(0, new CloudBundle { Source = "~/css/critical.css" });
Bundle options
| Property | Default | Description |
|---|---|---|
Source |
required | Relative (~/) or absolute (http) URL of the asset. |
MinOnRelease |
true |
Inserts .min. before the file extension in non-Development environments. |
AppendVersion |
true |
Appends a content-based ?v= query string for cache busting. |
UseMapping |
true |
Resolves the path through the static asset manifest; falls back to IFileVersionProvider when false. |
Defer |
true |
Adds the defer attribute to <script> tags. |
Async |
false |
Adds the async attribute to <script> tags. |
AddOns |
null |
Freeform attribute string appended verbatim to the rendered tag. |
Global bundles
Bundles added to config.PageDefaults are injected on every page before any per-page bundles:
builder.Services.AddCloudWeb(config =>
{
config.PageDefaults
.AppendBundle("~/css/site.css")
.AppendBundle("~/js/site.js");
});
MVC / Razor views
In MVC, use the @Html.Bundle() HTML helper extension in views:
// Controller
public class HomeController(CloudPage cloudPage) : CloudController(cloudPage)
{
public IActionResult Index()
{
CloudPage("Home Page");
return View();
}
}
@Html.Bundle("~/css/site.css")
CDN Features
Add a CloudPageFeatures flag to CloudPage and the corresponding CDN dependency is injected automatically into the page <head>. Multiple features can be added in a single call.
CloudPage.AddFeature(CloudPageFeatures.Maps);
CloudPage.AddFeature(CloudPageFeatures.JQuery);
// Or multiple at once:
CloudPage.AddFeatures(CloudPageFeatures.Maps, CloudPageFeatures.JQuery);
Available features
| Feature | CDN dependency injected |
|---|---|
CloudPageFeatures.Maps |
Azure Maps SDK v2 — atlas.min.css + atlas.min.js |
CloudPageFeatures.TextEditor |
TinyMCE 5 — tinymce.min.js |
CloudPageFeatures.JQuery |
jQuery 3.6.4 — jquery-3.6.4.min.js |
Legacy exports shim
Some older CommonJS modules expect a global exports object. Enable the compatibility shim with:
CloudPage.SetAddLegacyExportsCreation(true);
This injects <script>var exports = {};</script> before all bundles on that page.
Robots Control
Control search engine indexing and link-following on a per-page basis. When both are true (the default), no robots tag is emitted.
// Prevent indexing on this page
CloudPage.SetIndexPage(false);
// Prevent following links on this page
CloudPage.SetFollowPage(false);
// Explicitly allow both (identical to the default)
CloudPage
.SetIndexPage(true)
.SetFollowPage(true);
Rendered output
IndexPage |
FollowPage |
Meta tag output |
|---|---|---|
true |
true |
(no robots tag emitted) |
false |
true |
<meta name="robots" content="noindex"> |
true |
false |
<meta name="robots" content="nofollow"> |
false |
false |
<meta name="robots" content="noindex, nofollow"> |
Automatic staging protection
Any request arriving from a host ending in azurewebsites.net automatically receives noindex, nofollow, regardless of per-page settings. This prevents staging or preview deployments from being indexed by search engines. The same logic applies in the MVC CloudController base class, ensuring consistent behaviour across Blazor and MVC applications.
Global defaults
builder.Services.AddCloudWeb(config =>
{
config.PageDefaults
.SetIndexPage(true)
.SetFollowPage(true);
});
Crawler Detection
CloudPage detects search engine crawlers on construction by matching the request User-Agent header against an extensive built-in list (CloudWebConfig.CrawlersUserAgents). The IsCrawler property is set to true if any substring matches.
Blazor usage
@inject CloudPage CloudPage
@if (CloudPage.IsCrawler)
{
// Serve a simplified, text-only response for crawlers
}
else
{
// Serve the full interactive experience
}
MVC usage
Use the IsCrawler() helper method on CloudController:
public class HomeController(CloudPage cloudPage) : CloudController(cloudPage)
{
public IActionResult Index()
{
if (IsCrawler())
return View("IndexSimple");
return View();
}
}
Detection coverage
The built-in list covers hundreds of user-agent substrings including:
| Category | Examples |
|---|---|
| Search engines | Googlebot, Bingbot, YandexBot, Baiduspider |
| Generic patterns | bot, crawler, spider |
| Download tools | wget, curl |
| SEO / auditing tools | AhrefsBot, BLEXBot, SemrushBot |
Configuration Reference
All configuration is passed to AddCloudWeb via CloudWebConfig:
| Property | Type | Description |
|---|---|---|
TitlePrefix |
string |
String prepended to every page title (applied when a per-page title is set). |
TitleSuffix |
string |
String appended to every page title (applied when a per-page title is set). |
StaticFilesBaseDirectory |
string? |
Base directory prefix stripped/prepended when resolving asset paths with IFileVersionProvider. |
PageDefaults |
CloudPage |
Default metadata, bundles, features, and robots settings applied to every page. |
Repository Structure
CloudWeb/
├── CloudWeb/ # Razor Class Library (NuGet package)
├── CloudWeb.Demo/ # Blazor demo application
├── CloudWeb.Nuget/ # Console app that packs and publishes the NuGet package via CloudPack
└── CloudWeb.Tests/ # xUnit test project
Publishing a New Version
CloudWeb.Nuget uses AngryMonkey.CloudMate (CloudPack) to pack and push the CloudWeb library to NuGet.
- Bump
<Version>inCloudWeb/CloudWeb.csproj. - Store your NuGet API key in user secrets for
CloudWeb.Nuget:
dotnet user-secrets --project CloudWeb.Nuget set "NuGetApiKey" "<your-key>"
- Run
CloudWeb.Nugetto pack and publish — the.nupkgis also saved toNugetPackages/:
dotnet run --project CloudWeb.Nuget/CloudWeb.Nuget.csproj
Local Development
Run tests:
dotnet test CloudWeb.Tests/CloudWeb.Tests.csproj
Run demo:
dotnet run --project CloudWeb.Demo/CloudWeb.Demo.csproj
License
Angry Monkey Cloud
This project follows the shared AI development instructions.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AngryMonkey.CloudMate (>= 1.3.6)
- Microsoft.AspNetCore.Components.WebAssembly.Server (>= 10.0.9)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on AngryMonkey.CloudWeb:
| Package | Downloads |
|---|---|
|
AngryMonkey.CloudWeb.MVC
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2.3.0 | 0 | 7/27/2026 | |
| 2.2.3 | 310 | 3/27/2024 | |
| 2.2.2 | 223 | 3/27/2024 | |
| 2.2.1 | 227 | 3/26/2024 | |
| 2.1.2 | 273 | 3/8/2024 | |
| 2.1.1 | 231 | 2/29/2024 | |
| 2.1.0 | 238 | 2/20/2024 | |
| 2.0.4 | 233 | 2/16/2024 | |
| 2.0.3 | 225 | 1/31/2024 | |
| 2.0.2 | 320 | 12/8/2023 | |
| 2.0.1 | 249 | 12/6/2023 | |
| 2.0.0 | 243 | 12/5/2023 | |
| 1.0.8 | 434 | 4/5/2023 | |
| 1.0.7 | 389 | 4/5/2023 | |
| 1.0.6 | 387 | 4/4/2023 | |
| 1.0.5 | 380 | 4/4/2023 | |
| 1.0.4 | 420 | 3/29/2023 | |
| 1.0.3 | 405 | 3/28/2023 | |
| 1.0.2 | 392 | 3/17/2023 |