Postnomic.Client.AspNetCore 1.9.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Postnomic.Client.AspNetCore --version 1.9.0
                    
NuGet\Install-Package Postnomic.Client.AspNetCore -Version 1.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Postnomic.Client.AspNetCore" Version="1.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Postnomic.Client.AspNetCore" Version="1.9.0" />
                    
Directory.Packages.props
<PackageReference Include="Postnomic.Client.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Postnomic.Client.AspNetCore --version 1.9.0
                    
#r "nuget: Postnomic.Client.AspNetCore, 1.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Postnomic.Client.AspNetCore@1.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Postnomic.Client.AspNetCore&version=1.9.0
                    
Install as a Cake Addin
#tool nuget:?package=Postnomic.Client.AspNetCore&version=1.9.0
                    
Install as a Cake Tool

Postnomic .NET Client SDK

The official .NET Client SDK for Postnomic -- the developer-first headless blog backend. Add a fully-featured blog to any .NET application with a single NuGet package and a few lines of code.

License: MIT .NET 10

Why Postnomic?

Postnomic gives you everything you need to run a blog -- content management, analytics, comments, multi-user collaboration, media hosting -- all exposed through a clean REST API. You build the frontend; we handle the backend.

  • REST API with full OpenAPI documentation
  • Multi-blog support with team roles and permissions
  • Built-in analytics, comment moderation, and content scheduling
  • EU-hosted infrastructure with GDPR compliance
  • Free tier to get started -- no credit card required

Sign up at www.postnomic.com and have your blog backend running in minutes.

Packages

Package Description NuGet
Postnomic.Client.Abstractions Interfaces, DTOs, and configuration options NuGet
Postnomic.Client HTTP client implementation with optional caching NuGet
Postnomic.Client.AspNetCore Drop-in Razor Pages Area for ASP.NET Core apps NuGet
Postnomic.Client.Blazor Blazor components for Server and WebAssembly NuGet

Quick Start

ASP.NET Core (Razor Pages / MVC)

dotnet add package Postnomic.Client.AspNetCore
// Program.cs
builder.Services.AddPostnomicBlog(options =>
{
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";
});
builder.Services.AddRazorPages();

var app = builder.Build();
// ...
app.MapRazorPages();
app.MapPostnomicBlog(); // also serves /blog/sitemap.xml and /blog/rss.xml

app.Run();

// That's it -- your blog is live at /blog

Add one line to your host layout's <head> (e.g. Pages/Shared/_Layout.cshtml) so the SDK can inject its per-page canonical/OpenGraph/JSON-LD tags -- see SEO below:

<head>
    ...
    @await RenderSectionAsync("PostnomicHead", required: false)
</head>

Blazor (Server / WebAssembly)

dotnet add package Postnomic.Client.Blazor
// Program.cs
builder.Services.AddPostnomicBlog(options =>
{
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";
});

HTTP Client Only

If you want full control over rendering, use the base client package:

dotnet add package Postnomic.Client
builder.Services.AddPostnomicClient(options =>
{
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";
});

// Inject IPostnomicBlogService anywhere
public class MyController(IPostnomicBlogService blog)
{
    public async Task<IActionResult> Index()
    {
        var posts = await blog.GetPostsAsync();
        return View(posts);
    }
}

Configuration

All packages are configured through PostnomicClientOptions:

builder.Services.AddPostnomicBlog(options =>
{
    // Required
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";

    // Optional: customize the blog URL path (default: /blog)
    options.BasePath = "/articles";

    // Optional: where the language code appears in URLs for translated posts (default: Suffix)
    options.LanguageRouteStyle = PostnomicLanguageRouteStyle.Suffix;

    // Optional: enable client-side caching
    options.Cache = new PostnomicCacheOptions
    {
        Enabled = true,
        PostListDuration = TimeSpan.FromMinutes(5),
        PostDetailDuration = TimeSpan.FromMinutes(10),
    };
});

Options reference

Option Type Default Description
BlogSlug string "" Required. URL-friendly slug of the blog this client targets.
ApiKey string "" Required. Sent as the X-Api-Key header on every request.
BaseUrl string "" Required. Base URL of the Postnomic API (no trailing slash).
BasePath string /blog Base path the blog is served at (Razor Pages) or linked under (Blazor).
ShowBranding bool false Renders a "Powered by Postnomic" footer; server-enforced value from your plan takes precedence.
LanguageRouteStyle PostnomicLanguageRouteStyle Suffix Where the language code appears in generated URLs. See Language route style below.
MarkupStyle PostnomicMarkupStyle Bootstrap CSS class vocabulary emitted by Postnomic-rendered markup. See Theming / MarkupStyle below.
Cache PostnomicCacheOptions? null Optional client-side in-memory caching.
AlternateUrlResolver Func<...>? null Obsolete -- superseded by IPostnomicAlternateUrlProvider. See Per-post hreflang alternates.

Every SDK service takes IOptions<PostnomicClientOptions> -- PostnomicBlogService, CachingPostnomicBlogService, PostnomicAuthoringService, both auth handlers, and the typed HttpClient registrations behind them. So no options callback may depend on a service that touches the SDK: configuring options with the DI-aware OptionsBuilder.Configure<TDep> overload self-recurses and throws ValueFactory attempted to access the Value property of this instance. Full reference: Client options.

Multi-Blog Support

Host multiple blogs in a single application using named registrations:

builder.Services.AddPostnomicBlog("engineering", options =>
{
    options.BlogSlug = "engineering-blog";
    options.ApiKey = "pk_live_eng_...";
    options.BasePath = "/engineering";
});

builder.Services.AddPostnomicBlog("product", options =>
{
    options.BlogSlug = "product-updates";
    options.ApiKey = "pk_live_prod_...";
    options.BasePath = "/product";
});

Features

The SDK gives you access to the full Postnomic API:

  • Posts -- list, filter by tag/category, full-text search, pagination
  • Post Detail -- full HTML content, metadata, author info, related posts
  • Comments -- threaded comments with configurable required fields
  • Tags & Categories -- full taxonomy support
  • Authors -- profiles with bio, social links, certifications, education
  • Popular Posts -- trending content based on analytics
  • Blog Info -- blog metadata, layout, and configuration
  • Multi-language posts -- request a specific translation, get /{lang}/ routes and hreflang metadata for free (see below)
  • Automatic SEO -- canonical, hreflang, OpenGraph, Twitter Card, and JSON-LD structured data on every blog page (see SEO)
  • Sitemap & RSS -- sitemap.xml and rss.xml for every registered blog via MapPostnomicBlog() (see Sitemap, RSS & robots.txt)
  • Client-Side Caching -- optional in-memory cache with per-resource TTLs and explicit invalidation via IPostnomicCacheControl
  • Theming -- opt into framework-free pn-* classes and a shipped --pn-* variable-driven stylesheet instead of Bootstrap (see Theming / MarkupStyle)

Multi-language posts

If a blog has posts translated into multiple languages, the SDK lets you request a specific language and exposes what's available so you can build language switchers and SEO metadata.

Requesting a language

GetPostsAsync and GetPostAsync both take an optional trailing language argument -- an ISO-639-1 code (e.g. "de"). It's sent to the API as ?lang=, and it's part of the cache key when client-side caching is enabled. Leave it null to get the blog's default language (or let the API resolve it from the Accept-Language header).

// Explicit language
var post = await blog.GetPostAsync("intro-to-docker", language: "de");

// Post list in a specific language
var posts = await blog.GetPostsAsync(language: "de");

PostnomicPostSummary and PostnomicPostDetail both expose:

  • Language -- the language actually served for this post (may differ from what you requested if no translation exists; the API falls back to the blog's default language rather than 404ing)
  • AvailableLanguages -- every language this post has content in

Language route style

{lang} is always constrained to exactly two lowercase letters (e.g. de, en). PostnomicClientOptions.LanguageRouteStyle (Postnomic.Client.Abstractions.PostnomicLanguageRouteStyle) controls where that segment appears in generated URLs -- for both Postnomic.Client.AspNetCore route templates and every link the SDK generates (PostnomicRouteBuilder, sitemap/RSS, hreflang alternates):

LanguageRouteStyle Index Post
Suffix (default) /blog, /blog/{lang} /blog/post/{slug}, /blog/{lang}/post/{slug}
Prefix /{lang}/blog (only) /{lang}/blog/post/{slug} (only)
None /blog (only) /blog/post/{slug} (only)
  • Suffix (default) preserves pre-1.2 behavior exactly: the default-language page is served bare, and every other language is available at a {basePath}/{lang} suffix.
  • Prefix puts {lang} before the base path (/de/blog/...). Under Prefix, every URL is language-prefixed; there is no bare /blog route.
  • None never emits or accepts a language segment; the API's own language resolution (?lang= query → Accept-Language header → blog default) decides what's served at the single bare route.
// Program.cs -- Prefix mode: /de/blog, /de/blog/post/{slug}, etc.
builder.Services.AddPostnomicBlog(options =>
{
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";
    options.LanguageRouteStyle = PostnomicLanguageRouteStyle.Prefix;
});

Blazor components

PostPage, BlogPage, and AuthorPage (Postnomic.Client.Blazor) all accept an optional Language parameter, which you bind to your own routed {lang} segment, plus honor the blog's configured LanguageRouteStyle when generating internal links (index, author, sidebar widgets).

SEO

Every Blog area page (Postnomic.Client.AspNetCore) and every Blazor page component (Postnomic.Client.Blazor) automatically emits a full SEO head for you, built by the shared PostnomicSeoBuilder:

  • Canonical URL -- self-referential per language (the de variant of a post canonicalizes to its own /de/... URL, not the default-language one)
  • Meta description -- from the post excerpt, falling back to a stripped/truncated content snippet
  • robots meta tag
  • hreflang alternates -- one <link rel="alternate"> per language the post/blog is available in, plus x-default
  • OpenGraph -- og:type, og:title, og:description, og:url, og:image, og:site_name, og:locale (de_DE/en_US/...), and article:published_time / article:author / article:tag on post pages
  • Twitter Card -- summary_large_image
  • JSON-LD -- a @graph of BlogPosting (post pages), Blog + ItemList (index), or ProfilePage (author pages), plus a BreadcrumbList on every page type

Per-post hreflang alternates

The SDK composes hreflang alternates by applying your LanguageRouteStyle to the post's own slug. That is correct only when every translation shares the original's slug -- and a translated slug is not derivable from the original's. It may be identical, suffixed, or fully translated (kurze-hoerbuechershort-audiobooks).

When your translations don't all share one slug, supply the real URLs by implementing IPostnomicAlternateUrlProvider:

public sealed class BlogAlternateUrlProvider(IPostnomicBlogService blog)
    : IPostnomicAlternateUrlProvider
{
    public async ValueTask<IReadOnlyList<(string Language, string Url)>?> GetAlternatesAsync(
        PostnomicPostDetail post, CancellationToken cancellationToken = default)
    {
        var alternates = new List<(string Language, string Url)>();
        foreach (var language in post.AvailableLanguages)
        {
            var translated = await blog.GetPostAsync(post.Slug, language, cancellationToken);
            if (translated is not null)
                alternates.Add((language, $"/blog/post/{translated.Slug}"));
        }

        return alternates.Count > 0 ? alternates : null;
    }
}
builder.Services.AddPostnomicAlternateUrlProvider<BlogAlternateUrlProvider>();

The SDK resolves it from DI at render time, so it may depend on IPostnomicBlogService -- unlike an options callback, which cannot. It's async, so no cache-warming pass is needed. It works identically in both hosting models.

Full guide: Per-post hreflang alternates.

ASP.NET Core (Razor Pages)

The Blog area pages render their SEO tags into a Razor section named PostnomicHead. Your host layout must render that section inside <head>, or none of the tags above will appear on the page:

@* Pages/Shared/_Layout.cshtml *@
<head>
    <meta charset="utf-8" />
    ...
    @await RenderSectionAsync("PostnomicHead", required: false)
</head>

required: false is important -- non-blog pages in your app don't define the section.

The low-level CanonicalUrl / AlternateLanguageUrls properties on PostModel are still available (they're relative-path, not absolute, and predate the automatic SEO head) if you need to build your own custom tags, but for the standard tag set above you don't need to touch them.

Blazor

Blazor needs no extra wiring beyond the <HeadOutlet /> every Blazor app's root component already has -- PostPage/BlogPage/AuthorPage render their SEO tags via <HeadContent>, which <HeadOutlet /> picks up automatically.

Theming / MarkupStyle

By default, every Postnomic-rendered page (Postnomic.Client.AspNetCore Razor Pages and every Postnomic.Client.Blazor component) emits Bootstrap utility classes (card, row, btn btn-primary, ...) -- this is PostnomicMarkupStyle.Bootstrap, the default, and it preserves pre-1.3 output byte-for-byte for existing consumers.

Opt into PostnomicMarkupStyle.Semantic to render framework-free pn-* classes instead, themed entirely through CSS custom properties:

builder.Services.AddPostnomicBlog(options =>
{
    options.BlogSlug = "my-blog";
    options.ApiKey = "pk_live_...";
    options.BaseUrl = "https://api.postnomic.com";
    options.MarkupStyle = PostnomicMarkupStyle.Semantic;
});

Both packages ship a ready-to-use stylesheet that styles every pn-* class purely from --pn-* variables. Include it once in your host layout's <head> (pick the package you actually reference -- only load one):

<link rel="stylesheet" href="_content/Postnomic.Client.AspNetCore/postnomic-blog.css" />

<link rel="stylesheet" href="_content/Postnomic.Client.Blazor/postnomic-blog.css" />

Rebrand the blog by overriding --pn-* variables under .pn-blog (the outermost container in Semantic mode) in your own stylesheet, loaded after postnomic-blog.css:

.pn-blog {
    --pn-primary: var(--brand);
    --pn-on-primary: #ffffff;
    --pn-font-heading: "Poppins", sans-serif;
    --pn-radius-lg: 4px;
}

Every variable the stylesheet declares (with its shipped default, on .pn-blog):

Variable Purpose
--pn-font Base font stack
--pn-font-heading Heading font stack (titles)
--pn-max-width Max width of the blog container
--pn-surface Card/widget background
--pn-surface-variant Secondary surface (tag pills, filter banner, code blocks)
--pn-text Primary text color
--pn-text-muted Secondary/muted text color
--pn-primary Brand/accent color (primary buttons, active pagination, category tags)
--pn-on-primary Text/icon color on top of --pn-primary
--pn-border Border color used throughout
--pn-link Link color (defaults to --pn-primary)
--pn-radius Small corner radius (buttons, tags, fields)
--pn-radius-lg Large corner radius (cards, widgets)
--pn-space-xs / --pn-space-sm / --pn-space-md / --pn-space-lg / --pn-space-xl Spacing scale used for gaps, padding, and margins

PostnomicMarkupStyle.Bootstrap mode does not load or need postnomic-blog.css -- keep styling it with your own Bootstrap theme/overrides as before.

Sitemap, RSS & robots.txt

Call app.MapPostnomicBlog() after app.MapRazorPages() to also serve, for every blog registered via AddPostnomicBlog (the default blog and every named one):

  • GET {basePath}/sitemap.xml -- every post plus the index page, with xhtml:link hreflang alternates per post
  • GET {basePath}/rss.xml -- the 20 most recent posts as an RSS 2.0 feed
app.MapRazorPages();
app.MapPostnomicBlog();

Optionally, call app.MapPostnomicRobots() to serve a GET /robots.txt that allows all crawling and lists a Sitemap: directive for every registered blog. This is opt-in: only call it if your host app doesn't already serve its own /robots.txt (mapping both registers two competing handlers for the same route).

app.MapPostnomicBlog();
app.MapPostnomicRobots();

These endpoints are ASP.NET Core-only (Postnomic.Client.AspNetCore); there is no Blazor equivalent.

Requirements

  • .NET 10.0 or later
  • A Postnomic account (sign up free)
  • An API key from your Postnomic dashboard

Project Structure

src/
  Postnomic.Client.Abstractions/   # Interfaces and DTOs (no dependencies)
  Postnomic.Client/                # HTTP client implementation
  Postnomic.Client.AspNetCore/     # Razor Pages integration
  Postnomic.Client.Blazor/         # Blazor component integration
tests/
  Postnomic.Client.Abstractions.Tests/
  Postnomic.Client.Tests/
  Postnomic.Client.AspNetCore.Tests/
  Postnomic.Client.Blazor.Tests/

Development

# Build
dotnet build Postnomic.Client.slnx

# Run tests
dotnet test Postnomic.Client.slnx

# Pack NuGet packages
dotnet pack Postnomic.Client.slnx -c Release

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License -- see the LICENSE file for details.

Guides


Built with care by ThreeB IT GmbH in Ibbenbueren, Germany.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.9.2 90 9/2/2026
1.9.0 145 8/19/2026
1.8.1 116 8/19/2026
1.8.0 338 8/18/2026
1.7.0 102 8/16/2026
1.6.1 120 8/6/2026
1.6.0 116 8/5/2026
1.5.1 218 7/22/2026
1.5.0 388 7/7/2026
1.4.4 121 7/3/2026
1.4.3 118 7/3/2026
1.4.2 105 7/3/2026
1.4.1 113 7/3/2026
1.3.0 129 7/3/2026
1.2.0 164 7/2/2026
1.1.0 111 7/2/2026
1.0.4 115 7/1/2026
1.0.3 121 6/18/2026
1.0.2 110 5/4/2026
1.0.1 117 3/23/2026
Loading failed