Document Management in .NET: Renditions & Secure Links
Storing an uploaded file is the easy 20%. The other 80% — thumbnails, typed metadata, and secure expiring share links — is what every team reinvents badly. Here is how Granit solves all three.
Storing an uploaded file is the easy 20%. The other 80% — thumbnails, typed metadata, and secure expiring share links — is what every team reinvents badly. Here is how Granit solves all three.
Regex-parsing an LLM's prose reply breaks the day the model rephrases. Ask for a typed object instead — your C# type becomes a JSON schema the model must satisfy.
You don't need a vector database vendor to ship retrieval-augmented generation in .NET. Here's semantic search end to end — embeddings, pgvector, similarity search, and a grounded LLM answer — on the Postgres you already run.
Finance wants the numbers in Power BI, refreshed daily — again. Instead of hand-building another CSV export, expose a governed OData v4 feed from your .NET app and let Power BI pull live data through the same security pipeline as your grids.
Tax is where naive SaaS billing quietly breaks. Here is how to get EU cross-border VAT, US sales tax, and historical invoice integrity right in a .NET SaaS — with real Granit code.
A hand-written fetch wrapper drifts from your .NET contract the moment someone renames a field. Here is how to make the backend contract the single source of truth for your React client — with type errors at build time instead of 500s in prod.
Recurring billing looks like a cron job that charges a card once a month. Then a customer upgrades on day 20, a card declines, and someone overpays. Here is how the hard parts actually work in .NET.
Recurring EU billing does not require a US payment gateway. Here is how a provider-agnostic design collects a finalized invoice over SEPA — no Stripe, no Mollie, no Cloud Act exposure — and stays retry-safe end to end.
You do not need a metering vendor to bill on usage. Here is the full in-process pipeline — event ingest, idempotent dedupe, watermark rollup, quota alerts, invoice — built on Granit.Metering and Granit.Invoicing.
Row-level, schema-per-tenant, or database-per-tenant? Here is how the three isolation strategies trade off — and how Granit gives you the safe default plus a mechanical path to strict isolation when a customer demands it.
Most .NET teams reach for a Python or Node sidecar the moment a device starts speaking MQTT. Here's how to keep the entire pipeline — broker connection, dedup, thresholds, time-series storage — in .NET 10, and why that stops being optional past 10 million rows a day.
Every chat, dashboard, or CMS eventually needs a green dot next to a name. Most teams build it twice: once for "is this person online", once for "who else is looking at this document" — and get the multi-tab and multi-pod edge cases wrong both times. Here's the primitive that replaces both.
A stolen session cookie passes every auth check you have. The signal that something is wrong isn't in the token — it's in the pattern: a login from Brussels, then São Paulo, eight minutes later. Here's how to catch it in .NET.
Turning a client IP into a city has two answers in .NET — a local MaxMind database or a remote lookup API — and each hides a trap. Here is the honest trade-off between offline speed and online freshness, the GDPR problem nobody mentions, and how to run both behind one resolver.
FAPI 2.0 is the security profile required by Open Banking, healthcare APIs, and government identity systems. Most teams think conformance takes months. Granit's OpenIddict server, BFF, and resource server implement every requirement — one flag enables the full profile.
Auth0 costs $240/month past 7 500 users. Keycloak needs its own VM and JVM tuning. Granit.OpenIddict ships as NuGet packages and runs in the same process as your API — full OIDC compliance, zero external services.
Rate limiting and bulkhead isolation are often mentioned together but solve different problems. This deep-dive explains both patterns, shows where they fail without the other, and walks through Granit's dual enforcement model — Redis-backed quotas for the time axis, concurrency limiters for the thread axis.
A server restart between your database commit and your HTTP POST drops the event forever. The fix is the transactional outbox pattern — persist the event in the same transaction as your data, relay it asynchronously, retry on failure. Here is how Granit.Webhooks implements it end-to-end.
Shipping to the EU means opt-in. Shipping to California means opt-out. Shipping to Brazil means opt-in again. Most teams hardcode GDPR and call it done. Here is how Granit resolves the right consent model automatically — per tenant, per request, without a line of conditional logic in your application code.
Your strict Content Security Policy breaks the moment one ASP.NET Core route serves HTML — Scalar, an admin page, a Keycloak iframe. The usual band-aid weakens every other endpoint. Granit's per-endpoint CSP composition keeps strict defaults strict and lets the package that mounts the UI declare its own relaxation.
Prototype with Channel
W3C Web Push lets the browser ring even when your tab is closed. With VAPID you do it without FCM, without APNs, and without a single US-hosted dependency. Here is the .NET 10 side, the React hook, and the 30 lines of service worker that ties them together.
Every line-of-business app eventually grows an "import CSV" button. Done wrong it OOMs on the first 100k row file. Done right it streams, validates, resolves identity, and hands the user a correction file for the rows that failed.
Stop sprinkling CreatedAt assignments across repositories and IsDeleted = false across queries. Five interceptors and one global filter make compliance the default — and ExecuteUpdate the only thing left to watch for.
Hangfire ships fast. Then production scales out, two nodes fire the same recurring job at the same time, the outbox you bolted on doesn't include the job table, and the audit log goes sideways. There is a better default in 2026.
LaunchDarkly is great. €0.0050 per MAU is less great when you have 200k tenants and want to gate "VideoConference" behind the Premium plan. Here is how to build LaunchDarkly's core value in your own .NET codebase — without the bill.
Every SaaS eventually needs invoices, contracts, monthly reports. The HTML-to-PDF route is cheaper, more flexible and easier to maintain than iText or QuestPDF — provided you build the pipeline right. Here's how.
Code reviews catch typos. They do not catch a controller quietly importing Microsoft.EntityFrameworkCore and bypassing your repository layer. Architecture tests do — at build time, on every PR. Here is a pragmatic playbook for .NET teams.
Wire Keycloak JWT Bearer authentication into a Granit API in five minutes — claims transformation, role mapping, back-channel logout, and an Admin authorization policy, all from one DependsOn.
From accidental client evaluation to N+1 queries and tracked reads, these are the five EF Core anti-patterns that turn 50 ms endpoints into 5-second ones — and the exact fix for each.
One PublishAsync call, nine delivery channels. How Granit fans out a single notification to email, SMS, WhatsApp, mobile push, web push, SignalR, SSE, Zulip, and the in-app inbox without rewriting your domain code.
ASP.NET Core dropped the synchronization context years ago. So why do every Granit library and the .NET runtime itself still write ConfigureAwait(false) on every await? Because libraries do not get to choose their host.
Two-tier caches are fast, but cross-pod invalidation is the part everyone gets wrong. Here is how the L1 + L2 + backplane pattern works, and how Granit wires it up with Redis.
Scattering validation across controllers, services, and entities produces inconsistent errors and missed cases. Pin it to the API boundary with FluentValidation and endpoint filters — and let the platform do the rest.
MediatR is in-process only — no outbox, no retry, no transport. Wolverine is the CQRS bus most teams actually need. Here is how Granit uses it.
EF Core InMemory and SQLite lie about PostgreSQL. Use Testcontainers to run ephemeral real databases in your integration tests — zero mocks, zero surprises in prod.
Wire Serilog, OpenTelemetry and the Grafana LGTM stack into a .NET 10 app in one call. Structured logs, distributed traces, metrics — all OTLP, all sovereign.
IOptions, IOptionsSnapshot, and IOptionsMonitor solve different problems. Picking the wrong one causes stale config, unnecessary allocations, or subtle concurrency bugs. Here is when to use each — with real examples from the Granit codebase.
Hardcoded connection strings and static API keys are a breach waiting to happen. HashiCorp Vault gives you dynamic credentials, transit encryption, and per-entity key isolation — and Granit integrates all three as a single module dependency.
A dropped connection, a mobile retry, a load-balancer timeout — any of these can duplicate a POST. Idempotency keys turn dangerous replays into harmless no-ops, and Granit ships the entire pattern as a single middleware.
.NET 10 makes SSE a first-class citizen with TypedResults.ServerSentEvents(). Build a complete real-time notification stream from HTTP primitives to React hooks — no WebSocket plumbing required.
Stop returning bare strings from your .NET APIs. RFC 7807 Problem Details is the standard for error responses — here is why it matters, what the spec actually says, and how Granit enforces it with zero boilerplate.
SSE, SignalR, or raw WebSockets? A practical decision framework for .NET real-time applications, with protocol deep-dives, trade-offs, and ready-to-use Granit code samples for each transport.
String interpolation in log calls allocates on every invocation, even when the log level is disabled. Source-generated [LoggerMessage] eliminates that cost entirely and gives you structured logs for free.
FullStackHero and Granit both target modular .NET applications, but their architectures, compliance stories, and extension models diverge sharply. Here is a fair, side-by-side comparison for architects choosing their next foundation.
Enterprise customers ask for a SOC 2 Type 2 report before signing. Here is how Granit's modules map to the five Trust Service Criteria — and what the framework cannot replace.
NIS 2 is now law across the EU. Here is what it requires from your .NET stack — and how Granit's supply chain pipeline and embedded security modules cover the key obligations out of the box.
Swashbuckle is unmaintained and has no OpenAPI 3.1 support. .NET 10 ships a native document generator. Here is how to replace Swagger UI with Scalar — and what Granit's ApiDocumentation module does for you automatically.
The new Granit.Mcp module family wraps the MCP C# SDK to expose your application capabilities to AI agents, with auto-discovery, RBAC authorization, and GDPR-compliant output sanitization.
Physical deletion breaks audit trails, conflicts with legal holds, and fails on append-only systems. Crypto-shredding solves all three: destroy the key, and the ciphertext becomes random noise. Here is how Granit implements it.
Your React app stores access tokens in localStorage. A compromised npm package reads them. Game over. Granit.Bff moves token handling to the server, so the browser only sees an HttpOnly cookie it cannot read.
Returning EF Core entities directly from your endpoints leaks internal structure, creates circular references, and exposes sensitive data. Use response records instead.
Granit ships 400+ NuGet packages with zero circular dependencies, enforced by Kahn's algorithm at startup and architecture tests at build time.
Build a complete CRUD module with Granit in under 10 minutes — from entity definition to validated endpoints with full audit trail and soft delete.
Granit is an open-source modular framework for .NET 10 — 400+ packages, zero circular dependencies, GDPR and ISO 27001 compliance built in. Here is why we built it and what it gives you.
Manual null checks are noisy and inconsistent. .NET provides built-in guard methods that are cleaner, faster, and throw the right exceptions every time.
GDPR compliance is not a checkbox. It is an architectural constraint that must be enforced at the framework level. Here is how Granit implements data minimization, right to erasure, and pseudonymization by default.
new Regex(..., RegexOptions.Compiled) allocates at startup and hides patterns from the JIT. Source-generated regex is faster, safer, and enforced at build time.
Shared DbContexts create hidden coupling between modules. Here is how Granit enforces database isolation per module — and why it makes microservice extraction mechanical.
DateTime.Now makes your code untestable and timezone-fragile. Here is how to replace it with TimeProvider and IClock for deterministic, test-friendly time.
Microservices are not a goal. They are a trade-off. Here is why Granit bets on the modular monolith as the default architecture — and how the framework makes extraction painless when the time comes.