Read @webhouse/cms content from ASP.NET Core — Razor Pages, MVC, or minimal APIs.
Setup
my-project/
cms.config.ts
content/
wwwroot/uploads/
Pages/
Services/Service class
Create Services/Webhouse.cs:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MyApp.Services;
public class WebhouseDocument
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
[JsonPropertyName("locale")]
public string? Locale { get; set; }
[JsonPropertyName("translationGroup")]
public string? TranslationGroup { get; set; }
[JsonPropertyName("data")]
public Dictionary<string, JsonElement> Data { get; set; } = new();
}
public class Webhouse
{
private readonly string _contentDir;
private static readonly JsonSerializerOptions _options = new()
{
PropertyNameCaseInsensitive = true,
};
public Webhouse(IWebHostEnvironment env)
{
_contentDir = Path.Combine(env.ContentRootPath, "content");
}
public List<WebhouseDocument> Collection(string name, string? locale = null)
{
var dir = Path.Combine(_contentDir, name);
if (!Directory.Exists(dir)) return new();
var docs = new List<WebhouseDocument>();
foreach (var file in Directory.GetFiles(dir, "*.json"))
{
try
{
var doc = JsonSerializer.Deserialize<WebhouseDocument>(File.ReadAllText(file), _options);
if (doc == null || doc.Status != "published") continue;
if (locale != null && doc.Locale != locale) continue;
docs.Add(doc);
}
catch (JsonException) { /* skip malformed */ }
}
return docs
.OrderByDescending(d => GetString(d, "date") ?? "")
.ToList();
}
public WebhouseDocument? Document(string collection, string slug)
{
var path = Path.Combine(_contentDir, collection, $"{slug}.json");
if (!File.Exists(path)) return null;
var doc = JsonSerializer.Deserialize<WebhouseDocument>(File.ReadAllText(path), _options);
return doc?.Status == "published" ? doc : null;
}
public WebhouseDocument? FindTranslation(WebhouseDocument doc, string collection)
{
if (string.IsNullOrEmpty(doc.TranslationGroup)) return null;
return Collection(collection).FirstOrDefault(other =>
other.TranslationGroup == doc.TranslationGroup && other.Locale != doc.Locale);
}
public static string? GetString(WebhouseDocument doc, string key)
{
return doc.Data.TryGetValue(key, out var v) && v.ValueKind == JsonValueKind.String
? v.GetString()
: null;
}
}Register in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton<Webhouse>();
var app = builder.Build();
app.UseStaticFiles();
app.MapRazorPages();
app.Run();Razor Page
// Pages/Blog/Post.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MyApp.Services;
public class PostModel : PageModel
{
private readonly Webhouse _webhouse;
public WebhouseDocument? Post { get; private set; }
public PostModel(Webhouse webhouse) => _webhouse = webhouse;
public IActionResult OnGet(string slug)
{
Post = _webhouse.Document("posts", slug);
return Post == null ? NotFound() : Page();
}
}@* Pages/Blog/Post.cshtml *@
@page "{slug}"
@model PostModel
@using MyApp.Services
<article>
<h1>@Webhouse.GetString(Model.Post!, "title")</h1>
<time>@Webhouse.GetString(Model.Post!, "date")</time>
<div class="prose">
@Html.Raw(Markdig.Markdown.ToHtml(Webhouse.GetString(Model.Post!, "content") ?? ""))
</div>
</article>Add Markdig for markdown rendering: dotnet add package Markdig
Minimal API alternative
var app = WebApplication.Create(args);
var wh = new Webhouse(app.Environment);
app.MapGet("/", () => wh.Collection("posts", "en"));
app.MapGet("/blog/{slug}", (string slug) =>
{
var post = wh.Document("posts", slug);
return post == null ? Results.NotFound() : Results.Ok(post);
});
app.Run();Serving media
ASP.NET Core's UseStaticFiles() serves from wwwroot/ by default. Either:
- Symlink
content/uploadstowwwroot/uploads, or - Add an additional static files middleware:
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "public/uploads")),
RequestPath = "/uploads",
});Caching
builder.Services.AddMemoryCache();
// In Webhouse class:
public List<WebhouseDocument> CollectionCached(string name, string? locale, IMemoryCache cache)
{
var key = $"webhouse:{name}:{locale}";
return cache.GetOrCreate(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
return Collection(name, locale);
}) ?? new();
}Next steps
- See the .NET example
- Learn about Framework-Agnostic Architecture