{
  "slug": "consume-dotnet",
  "title": "Consume from C# / .NET",
  "description": "Read @webhouse/cms content from ASP.NET Core — Razor Pages, MVC, or minimal APIs.",
  "category": "consumers",
  "order": 14,
  "locale": "en",
  "translationGroup": "dd70c376-cb4c-47ae-8274-6cf50571d565",
  "helpCardId": null,
  "content": "## Setup\n\n```\nmy-project/\n  cms.config.ts\n  content/\n  wwwroot/uploads/\n  Pages/\n  Services/\n```\n\n## Service class\n\nCreate `Services/Webhouse.cs`:\n\n```csharp\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace MyApp.Services;\n\npublic class WebhouseDocument\n{\n    [JsonPropertyName(\"id\")]\n    public string Id { get; set; } = string.Empty;\n\n    [JsonPropertyName(\"slug\")]\n    public string Slug { get; set; } = string.Empty;\n\n    [JsonPropertyName(\"status\")]\n    public string Status { get; set; } = string.Empty;\n\n    [JsonPropertyName(\"locale\")]\n    public string? Locale { get; set; }\n\n    [JsonPropertyName(\"translationGroup\")]\n    public string? TranslationGroup { get; set; }\n\n    [JsonPropertyName(\"data\")]\n    public Dictionary<string, JsonElement> Data { get; set; } = new();\n}\n\npublic class Webhouse\n{\n    private readonly string _contentDir;\n    private static readonly JsonSerializerOptions _options = new()\n    {\n        PropertyNameCaseInsensitive = true,\n    };\n\n    public Webhouse(IWebHostEnvironment env)\n    {\n        _contentDir = Path.Combine(env.ContentRootPath, \"content\");\n    }\n\n    public List<WebhouseDocument> Collection(string name, string? locale = null)\n    {\n        var dir = Path.Combine(_contentDir, name);\n        if (!Directory.Exists(dir)) return new();\n\n        var docs = new List<WebhouseDocument>();\n        foreach (var file in Directory.GetFiles(dir, \"*.json\"))\n        {\n            try\n            {\n                var doc = JsonSerializer.Deserialize<WebhouseDocument>(File.ReadAllText(file), _options);\n                if (doc == null || doc.Status != \"published\") continue;\n                if (locale != null && doc.Locale != locale) continue;\n                docs.Add(doc);\n            }\n            catch (JsonException) { /* skip malformed */ }\n        }\n\n        return docs\n            .OrderByDescending(d => GetString(d, \"date\") ?? \"\")\n            .ToList();\n    }\n\n    public WebhouseDocument? Document(string collection, string slug)\n    {\n        var path = Path.Combine(_contentDir, collection, $\"{slug}.json\");\n        if (!File.Exists(path)) return null;\n        var doc = JsonSerializer.Deserialize<WebhouseDocument>(File.ReadAllText(path), _options);\n        return doc?.Status == \"published\" ? doc : null;\n    }\n\n    public WebhouseDocument? FindTranslation(WebhouseDocument doc, string collection)\n    {\n        if (string.IsNullOrEmpty(doc.TranslationGroup)) return null;\n        return Collection(collection).FirstOrDefault(other =>\n            other.TranslationGroup == doc.TranslationGroup && other.Locale != doc.Locale);\n    }\n\n    public static string? GetString(WebhouseDocument doc, string key)\n    {\n        return doc.Data.TryGetValue(key, out var v) && v.ValueKind == JsonValueKind.String\n            ? v.GetString()\n            : null;\n    }\n}\n```\n\n## Register in Program.cs\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddRazorPages();\nbuilder.Services.AddSingleton<Webhouse>();\n\nvar app = builder.Build();\napp.UseStaticFiles();\napp.MapRazorPages();\napp.Run();\n```\n\n## Razor Page\n\n```csharp\n// Pages/Blog/Post.cshtml.cs\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing MyApp.Services;\n\npublic class PostModel : PageModel\n{\n    private readonly Webhouse _webhouse;\n    public WebhouseDocument? Post { get; private set; }\n\n    public PostModel(Webhouse webhouse) => _webhouse = webhouse;\n\n    public IActionResult OnGet(string slug)\n    {\n        Post = _webhouse.Document(\"posts\", slug);\n        return Post == null ? NotFound() : Page();\n    }\n}\n```\n\n```razor\n@* Pages/Blog/Post.cshtml *@\n@page \"{slug}\"\n@model PostModel\n@using MyApp.Services\n\n<article>\n  <h1>@Webhouse.GetString(Model.Post!, \"title\")</h1>\n  <time>@Webhouse.GetString(Model.Post!, \"date\")</time>\n  <div class=\"prose\">\n    @Html.Raw(Markdig.Markdown.ToHtml(Webhouse.GetString(Model.Post!, \"content\") ?? \"\"))\n  </div>\n</article>\n```\n\nAdd Markdig for markdown rendering: `dotnet add package Markdig`\n\n## Minimal API alternative\n\n```csharp\nvar app = WebApplication.Create(args);\nvar wh = new Webhouse(app.Environment);\n\napp.MapGet(\"/\", () => wh.Collection(\"posts\", \"en\"));\napp.MapGet(\"/blog/{slug}\", (string slug) =>\n{\n    var post = wh.Document(\"posts\", slug);\n    return post == null ? Results.NotFound() : Results.Ok(post);\n});\n\napp.Run();\n```\n\n## Serving media\n\nASP.NET Core's `UseStaticFiles()` serves from `wwwroot/` by default. Either:\n\n1. Symlink `content/uploads` to `wwwroot/uploads`, or\n2. Add an additional static files middleware:\n\n```csharp\napp.UseStaticFiles(new StaticFileOptions\n{\n    FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, \"public/uploads\")),\n    RequestPath = \"/uploads\",\n});\n```\n\n## Caching\n\n```csharp\nbuilder.Services.AddMemoryCache();\n\n// In Webhouse class:\npublic List<WebhouseDocument> CollectionCached(string name, string? locale, IMemoryCache cache)\n{\n    var key = $\"webhouse:{name}:{locale}\";\n    return cache.GetOrCreate(key, entry =>\n    {\n        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);\n        return Collection(name, locale);\n    }) ?? new();\n}\n```\n\n## Next steps\n\n- See the [.NET example](https://github.com/webhousecode/cms/tree/main/examples/consumers/dotnet-blog)\n- Learn about [Framework-Agnostic Architecture](/docs/framework-agnostic)",
  "excerpt": "Setup\n\n\nmy-project/\n  cms.config.ts\n  content/\n  wwwroot/uploads/\n  Pages/\n  Services/\n\n\n Service class\n\nCreate Services/Webhouse.cs:\n\ncsharp\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace MyApp.Services;\n\npublic class WebhouseDocument\n{\n    [JsonPropertyName(\"id\")]\n    pub",
  "seo": {
    "metaTitle": "Consume from C# / .NET — webhouse.app Docs",
    "metaDescription": "Read @webhouse/cms content from ASP.NET Core — Razor Pages, MVC, or minimal APIs.",
    "keywords": [
      "webhouse",
      "cms",
      "dotnet",
      "csharp",
      "aspnet",
      "consumer"
    ]
  },
  "createdAt": "2026-04-08T12:00:00.000Z",
  "updatedAt": "2026-04-08T12:00:00.000Z"
}