Granit Bundles — one PackageReference, not thirty
The problem
Section titled “The problem”Granit ships 420 packages. That level of granularity is great for control
but overwhelming when you’re starting a fresh project — you shouldn’t have to know
the full module graph to get a working API up. Bundles solve this: a bundle is a
NuGet meta-package with no code of its own, just a curated set of PackageReference
entries you’d otherwise type by hand. One package reference, one Add*() call, and
the entire feature group is wired correctly.
| Pain | Bundle’s answer |
|---|---|
| Onboarding asks for “the right modules to start” | One meta-package per job |
| Manually composing 8 references for a typical Web API | Granit.Bundle.Api |
Forgetting Granit.Persistence.EntityFrameworkCore and crashing at first save | Bundles include the persistence root |
| Mixing up which OpenIddict packages go together | Granit.Bundle.OpenIddict ships the full server + EFC + endpoints + jobs |
| Maintaining your “starter template” | Bump the bundle version; transitive deps follow |
Available bundles
Section titled “Available bundles”| Bundle | Ships from | Use case |
|---|---|---|
| Essentials | granit-dotnet | Every project — the foundation |
| Api | granit-dotnet | REST API projects — adds versioning + OpenAPI + Idempotency + Localization + Redis caching |
| OpenIddict | granit-dotnet | Authorization server (issuer of access tokens) — adds the full OpenIddict stack + local identity |
| Notifications | granit-dotnet | Multi-channel notifications — Email (SMTP) + SignalR with persistence + admin API |
| SaaS | granit-business | Multi-tenant operations — MultiTenancy + Features + RateLimiting + Bulkhead |
| Documents | granit-business | Document generation — Templating (Scriban) + DocumentGeneration (PDF, Excel) with persistence |
Bundle contents
Section titled “Bundle contents”Granit.Bundle.Essentials
Section titled “Granit.Bundle.Essentials”The foundation. Every Granit project starts here.
| Package | Role |
|---|---|
Granit | Core abstractions (ICurrentUserService, ICurrentTenant, base entity types) |
Granit.Timing | TimeProvider injection |
Granit.Guids | Sequential GUID generation |
Granit.Validation | FluentValidation wiring |
Granit.Persistence.EntityFrameworkCore | AddGranitDbContext<T>, UseGranitInterceptors, auto-discovered interceptors |
Granit.Observability | Serilog + OpenTelemetry tracing/metrics, OTLP export |
Granit.Http.Cors | Standardized CORS with startup validation |
Granit.Http.ExceptionHandling | RFC 7807 Problem Details, 4xx/5xx message masking |
Granit.Http.ResponseCompression | gzip / brotli by default |
Granit.Http.SecurityHeaders | Per-endpoint CSP composition, HSTS, X-Content-Type-Options |
Granit.Http.SecurityHeaders.Abstractions | Contracts for the ICspContributor pattern — referenced by packages that declare their own CSP relaxations (transitive via Granit.Http.SecurityHeaders) |
Granit.Diagnostics | Activity sources registry, health-check primitives |
Granit.Http.SecurityHeaders.Endpoints ships separately (not bundled) and adds
a /security-headers/csp audit endpoint gated by Diagnostics.Monitoring.Read
for security auditors. Reference it explicitly when you need it.
Granit.Bundle.Api
Section titled “Granit.Bundle.Api”Everything in Essentials + the REST API surface.
| Package | Role |
|---|---|
Granit.Bundle.Essentials (transitive) | The foundation |
Granit.Http.ApiVersioning | URL / header / query versioning |
Granit.Http.ApiDocumentation | OpenAPI 3.1 + Scalar UI, versioned per major API |
Granit.Http.Idempotency | Stripe-style replay-safe Idempotency-Key middleware |
Granit.Localization + .EntityFrameworkCore | Accept-Language resolution, runtime overrides |
Granit.Caching.StackExchangeRedis | Distributed cache backbone for output caching, idempotency, sessions |
Granit.Bundle.OpenIddict
Section titled “Granit.Bundle.OpenIddict”A complete authorization server (token issuer). Use this when your app owns
the identity store; if you only need to consume tokens issued by Keycloak /
EntraId / Cognito, use Granit.Authentication.JwtBearer.<provider> directly.
| Package | Role |
|---|---|
Granit.Authentication.OpenIddict | JWT Bearer validation against the local OpenIddict issuer |
Granit.OpenIddict | OpenIddict core |
Granit.OpenIddict.Server | Token + authorization + introspection endpoints |
Granit.OpenIddict.EntityFrameworkCore | EF-backed application/authorization/scope stores |
Granit.OpenIddict.Endpoints | Admin API for application/scope management |
Granit.OpenIddict.BackgroundJobs | Token cleanup + key rotation jobs |
Granit.Identity.Local.AspNetIdentity | ASP.NET Identity-backed LocalIdentity user store |
Granit.Identity.Local.Endpoints | Self-service /account/* endpoints (register, reset, change password) |
Granit.Bundle.Notifications
Section titled “Granit.Bundle.Notifications”| Package | Role |
|---|---|
Granit.Notifications | Channel-agnostic dispatch (INotificationPublisher, types, preferences) |
Granit.Notifications.EntityFrameworkCore | Delivery log + user preferences persistence |
Granit.Notifications.Endpoints | Admin / user preference API |
Granit.Notifications.Email | Channel-neutral email contract |
Granit.Notifications.Email.Smtp | SMTP provider (MailKit) |
Granit.Notifications.SignalR | Real-time browser push via SignalR + Redis backplane |
Add other providers à la carte: Granit.Notifications.Email.{SendGrid,Brevo,Scaleway,Mailgun,Postmark,AwsSes,AzureCommunication}, Granit.Notifications.Sms.*, Granit.Notifications.WebPush, Granit.Notifications.MobilePush.*, Granit.Notifications.WhatsApp, Granit.Notifications.Zulip.
Granit.Bundle.SaaS (granit-business)
Section titled “Granit.Bundle.SaaS (granit-business)”Focused multi-tenant infrastructure. Layer it on top of Granit.Bundle.Api.
| Package | Role |
|---|---|
Granit.MultiTenancy | Tenant resolution (domain, header, JWT claim), ICurrentTenant, side-aware permissions (MultiTenancySides) |
Granit.Features | Multi-level feature flags (Tenant > Plan > Default) |
Granit.Features.EntityFrameworkCore | Persistent flag overrides |
Granit.RateLimiting | Per-tenant rate limiting (sliding/fixed window, token bucket, concurrency) |
Granit.Http.Bulkhead | Per-tenant concurrency isolation |
Granit.Bundle.Documents (granit-business)
Section titled “Granit.Bundle.Documents (granit-business)”| Package | Role |
|---|---|
Granit.Templating | ITemplateEngine abstraction + lifecycle (draft/review/publish) |
Granit.Templating.Scriban | Scriban-based engine (Liquid-compatible, sandboxed) |
Granit.Templating.EntityFrameworkCore | Template + version persistence |
Granit.DocumentGeneration | IDocumentGenerator facade — two-stage pipeline (text render → binary) |
Granit.DocumentGeneration.Pdf | HTML → PDF via headless browser, PDF/A-3b |
Granit.DocumentGeneration.Excel | ClosedXML-based engine |
Reference the bundle package and call the corresponding method on the Granit builder:
builder.AddGranit<AppModule>(granit => granit .AddApi());builder.AddGranit<AppModule>(granit => granit .AddApi() .AddSaaS() .AddOpenIddict() .AddNotifications());builder.AddGranit<AppModule>(granit => granit .AddEssentials() .AddModule<GranitTemplatingScribanModule>() .AddModule<GranitDocumentGenerationPdfModule>());Bundles are additive
Section titled “Bundles are additive”You can combine any number of bundles in a single call. Deduplication is automatic
— the [DependsOn] graph tracks what’s already registered and skips duplicates.
// Api includes Essentials; layering SaaS on top is safe — no double registration.builder.AddGranit<AppModule>(granit => granit .AddApi() .AddSaaS() .AddNotifications() .AddDocuments());When NOT to use bundles
Section titled “When NOT to use bundles”Bundles are convenience, not requirement. If your project only needs three specific packages, reference them directly:
<PackageReference Include="Granit" /><PackageReference Include="Granit.Observability" /><PackageReference Include="Granit.Http.ExceptionHandling" />This keeps your dependency footprint minimal and avoids pulling in modules you’ll never configure.
Further reading
Section titled “Further reading”- Module System — how
[DependsOn]and the module graph work - Framework vs modules — what lives in
granit-dotnetvsgranit-business - Dependency Graph — full visual map of every package