CShells 0.0.5
dotnet add package CShells --version 0.0.5
NuGet\Install-Package CShells -Version 0.0.5
<PackageReference Include="CShells" Version="0.0.5" />
<PackageVersion Include="CShells" Version="0.0.5" />
<PackageReference Include="CShells" />
paket add CShells --version 0.0.5
#r "nuget: CShells, 0.0.5"
#:package CShells@0.0.5
#addin nuget:?package=CShells&version=0.0.5
#tool nuget:?package=CShells&version=0.0.5
CShells
A lightweight, extensible shell and feature system for .NET projects that lets you build modular and multi-tenant apps with per-shell DI containers and config-driven features.
Features
- Multi-shell architecture - Each shell has its own isolated DI container
- Feature-based modularity - Features are discovered automatically via attributes
- Dependency resolution - Features can depend on other features with topological ordering
- Configuration-driven - Shells and their features are configured via appsettings.json
- ASP.NET Core integration - Middleware for per-request shell resolution
Use Cases
CShells is useful whenever you want clear modular boundaries, configurable feature sets, and isolated dependency graphs inside a .NET application.
Modular Monoliths with Pluggable Features
Model each functional area (e.g., Core, Billing, Reporting) as a feature and group them into shells that can be enabled or disabled via configuration. This keeps a monolithic codebase modular and lets you turn features on or off without code changes.
Multitenant Apps with Per-Tenant Feature Toggles
Treat each tenant as a shell with its own configuration and feature set. You can roll out features gradually, offer different capabilities per tenant, and keep tenant-specific services (e.g., integrations, branding, limits) isolated in per-shell DI containers.
Single-Tenant Apps with Environment- or Plan-Based Features
Use shells to represent different plans (Basic, Pro, Enterprise) or environments (Development, Staging, Production), each enabling a different set of features. This lets you keep one codebase while varying behavior and dependencies based on environment, subscription level, or other criteria.
Modular Frameworks and Platforms (CMS, CRM, Orchard Core/ABP-like)
Build your own modular application framework where modules are implemented as features discovered at startup. CShells’ feature discovery and ordering, combined with per-shell DI, make it a good fit for CMSs, CRMs, ERP-style systems, and frameworks similar to Orchard Core or ABP.
White-Label SaaS and Branded Deployments
Model each brand or deployment as a shell with its own enabled features, configuration, and DI registrations. You can share the same core features while varying branding, integrations, or compliance-related components per shell.
Extensible Line-of-Business Apps with Plugins
Expose extension points as features that can be discovered from additional assemblies and loaded into shells. This enables plugin-style architectures where internal teams or third parties can add capabilities without modifying the core app.
API Gateways and Backend-for-Frontend (BFF) Layers
Use shells to represent different API surfaces (mobile, web, partner, admin) with their own middleware, endpoints, and policies. Each shell can have tailored dependencies and configuration while still sharing common infrastructure and hosting.
Gradual Modularization of Legacy Apps
Introduce CShells into an existing application and start moving functionality into features and shells incrementally. This allows you to modularize and isolate areas of a legacy system over time without a big-bang rewrite.
Packages
CShells provides multiple NuGet packages for different use cases:
| Package | Description | When to Use |
|---|---|---|
| CShells.Abstractions | Core interfaces and models (IShellFeature, ShellSettings, ShellId) |
Reference this in feature class libraries to avoid depending on the full framework |
| CShells.AspNetCore.Abstractions | ASP.NET Core interfaces (IWebShellFeature) |
Reference this in ASP.NET Core feature class libraries for web endpoint support |
| CShells | Core framework implementation | Reference this in your main application project |
| CShells.AspNetCore | ASP.NET Core integration (middleware, routing, resolvers) | Reference this in your ASP.NET Core application project |
| CShells.Providers.FluentStorage | FluentStorage-based shell configuration provider | Use when loading shell configurations from disk, cloud storage, etc. |
Recommended Project Structure
YourSolution/
├── src/
│ ├── YourApp/ # Main ASP.NET Core application
│ │ └── YourApp.csproj # References: CShells, CShells.AspNetCore, YourApp.Features
│ └── YourApp.Features/ # Feature definitions library
│ └── YourApp.Features.csproj # References: CShells.AspNetCore.Abstractions only
This structure allows your feature library to remain lightweight with minimal dependencies, while your main application references the full CShells implementation.
Quick Start
1. Create a Feature
Features implement IShellFeature for service registration:
using CShells.Features;
using Microsoft.Extensions.DependencyInjection;
public class CoreFeature : IShellFeature
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITimeService, TimeService>();
}
}
For ASP.NET Core applications with HTTP endpoints, implement IWebShellFeature:
using CShells.AspNetCore.Features;
using Microsoft.Extensions.DependencyInjection;
public class ApiFeature : IWebShellFeature
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IApiService, ApiService>();
}
public void MapEndpoints(IEndpointRouteBuilder endpoints, IHostEnvironment? environment)
{
endpoints.MapGet("api/status", () => new { Status = "OK" });
}
}
Best Practice: Define your features in a separate class library that only references CShells.Abstractions (or CShells.AspNetCore.Abstractions for web features). This keeps your feature definitions lightweight and independent of the full framework implementation.
The [ShellFeature] attribute is optional. Use it only when you need to:
- Specify an explicit feature name (otherwise class name is used)
- Provide a display name
- Declare feature dependencies
- Set metadata
using CShells.Features;
// Without attribute - feature name is "WeatherFeature" (derived from class name)
public class WeatherFeature : IShellFeature
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IWeatherService, WeatherService>();
}
}
// With attribute - explicit name "Weather", display name, and dependencies
[ShellFeature("Weather", DisplayName = "Weather API", DependsOn = ["Core"])]
public class WeatherFeature : IShellFeature
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IWeatherService, WeatherService>();
}
}
Features can access ShellSettings via constructor injection:
using CShells;
using CShells.Features;
public class WeatherFeature : IShellFeature
{
private readonly ShellSettings _shellSettings;
public WeatherFeature(ShellSettings shellSettings)
{
_shellSettings = shellSettings;
}
public void ConfigureServices(IServiceCollection services)
{
// Access shell configuration
var apiKey = _shellSettings.Properties.GetValue<string>("WeatherApiKey");
services.AddSingleton<IWeatherService>(new WeatherService(apiKey));
}
}
2. Configure Shells
Option A: Using appsettings.json (default section name: CShells):
{
"CShells": {
"Shells": [
{
"Name": "Default",
"Features": [ "Core", "Weather" ],
"Properties": {
"WebRouting": {
"Path": ""
}
}
},
{
"Name": "Admin",
"Features": [ "Core", "Admin" ],
"Properties": {
"WebRouting": {
"Path": "admin"
}
}
}
]
}
}
You can also override the configuration section name via builder.AddShells("MySection").
Option B: Using JSON files with FluentStorage:
Create JSON files in a Shells folder (e.g., Default.json, Admin.json):
{
"Name": "Default",
"Features": [ "Core", "Weather" ],
"Properties": {
"WebRouting": {
"Path": ""
}
}
}
Then configure the provider:
using FluentStorage;
using CShells.Providers.FluentStorage;
var builder = WebApplication.CreateBuilder(args);
var shellsPath = Path.Combine(builder.Environment.ContentRootPath, "Shells");
var blobStorage = StorageFactory.Blobs.DirectoryFiles(shellsPath);
builder.AddShells(cshells =>
{
cshells.WithFluentStorageProvider(blobStorage);
});
Option C: Code-first configuration:
builder.AddShells(cshells =>
{
cshells.AddShell("Default", shell => shell
.WithFeatures("Core", "Weather")
.WithPath(""));
cshells.AddShell("Admin", shell => shell
.WithFeatures("Core", "Admin")
.WithPath("admin"));
cshells.WithInMemoryShells();
});
3. Register CShells in Program.cs
Simple setup (reads from appsettings.json):
var builder = WebApplication.CreateBuilder(args);
// Register CShells from configuration (default section: CShells)
// Scans all loaded assemblies for features by default
builder.AddShells();
var app = builder.Build();
// Configure middleware and endpoints for all shells
app.MapShells();
app.Run();
You can optionally specify assemblies to scan for features:
builder.AddShells(assemblies: [typeof(Program).Assembly]);
FluentStorage setup (reads from Shells folder):
using FluentStorage;
using CShells.Providers.FluentStorage;
var builder = WebApplication.CreateBuilder(args);
var shellsPath = Path.Combine(builder.Environment.ContentRootPath, "Shells");
var blobStorage = StorageFactory.Blobs.DirectoryFiles(shellsPath);
builder.AddShells(cshells =>
{
cshells.WithFluentStorageProvider(blobStorage);
});
var app = builder.Build();
app.MapShells();
app.Run();
Advanced setup with custom resolvers:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCShellsAspNetCore(cshells =>
{
cshells.WithConfigurationProvider(builder.Configuration);
cshells.WithWebRoutingResolver(options =>
{
// Configure web routing options
options.ExcludePaths = new[] { "/api", "/health" };
options.HeaderName = "X-Tenant-Id";
});
});
var app = builder.Build();
app.MapShells();
app.Run();
Key Capabilities
- IShellFeature - Basic interface for service registration in features
- IWebShellFeature - Extends
IShellFeatureto add HTTP endpoint registration viaMapEndpoints() - Optional
[ShellFeature]attribute - Use only when you need explicit names, display names, dependencies, or metadata - Automatic endpoint routing -
MapShells()handles middleware and endpoint registration in one call - Shell path prefixes - Routes are automatically prefixed based on the
WebRouting.Pathproperty - Per-shell DI containers - Each shell has its own isolated service provider with shell-specific services
- Multiple configuration sources - Configure shells via appsettings.json, external JSON files, or code
- Flexible shell resolution - Built-in path and host resolvers, plus extensibility for custom strategies
- Feature dependencies - Features can depend on other features with automatic topological ordering
- Constructor injection of ShellSettings - Features can access their shell's configuration via constructor
- Runtime shell management - Add, update, or remove shells at runtime without restarting the application
Configuration
Shell Settings Providers
CShells supports multiple ways to configure shells:
1. Configuration-based (appsettings.json)
builder.AddShells(); // Uses default "CShells" section
// or
builder.AddShells("MyCustomSection");
// or
builder.Services.AddCShellsAspNetCore(cshells =>
{
cshells.WithConfigurationProvider(builder.Configuration, "CShells");
});
2. FluentStorage (JSON files from disk/cloud)
using CShells.Providers.FluentStorage;
var blobStorage = StorageFactory.Blobs.DirectoryFiles("./Shells");
builder.AddShells(cshells =>
{
cshells.WithFluentStorageProvider(blobStorage);
});
3. Code-first (In-memory)
builder.AddShells(cshells =>
{
cshells.AddShell("Default", shell => shell
.WithFeatures("Core", "Weather")
.WithPath("")
.WithProperty("Title", "Default Site"));
cshells.WithInMemoryShells();
});
4. Custom Provider
public class DatabaseShellSettingsProvider : IShellSettingsProvider
{
public async Task<IEnumerable<ShellSettings>> GetAllAsync()
{
// Load from database, API, etc.
return Enumerable.Empty<ShellSettings>();
}
}
builder.AddShells(cshells =>
{
cshells.WithProvider<DatabaseShellSettingsProvider>();
});
Shell Context Scopes & Background Work
Shell context scopes provide a way to create scoped services within a shell's service provider. This is particularly useful for background workers or other services that need to execute work in the context of each shell.
Creating Shell Context Scopes
Use IShellContextScopeFactory to create scopes for shell contexts:
using CShells;
public class MyService
{
private readonly IShellHost _shellHost;
private readonly IShellContextScopeFactory _scopeFactory;
public MyService(IShellHost shellHost, IShellContextScopeFactory scopeFactory)
{
_shellHost = shellHost;
_scopeFactory = scopeFactory;
}
public void DoWork()
{
foreach (var shell in _shellHost.AllShells)
{
using var scope = _scopeFactory.CreateScope(shell);
// Resolve scoped services from the shell's service provider
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
myService.Execute();
}
}
}
Background Worker Example
Here's an example of a background service that executes work for each shell:
using CShells;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public class ShellBackgroundWorker : BackgroundService
{
private readonly IShellHost _shellHost;
private readonly IShellContextScopeFactory _scopeFactory;
private readonly ILogger<ShellBackgroundWorker> _logger;
public ShellBackgroundWorker(
IShellHost shellHost,
IShellContextScopeFactory scopeFactory,
ILogger<ShellBackgroundWorker> logger)
{
_shellHost = shellHost;
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foreach (var shell in _shellHost.AllShells)
{
using var scope = _scopeFactory.CreateScope(shell);
// Execute work in the shell's context
_logger.LogInformation("Background work executed for shell '{ShellId}'", shell.Id.Name);
// Resolve and use scoped services
var service = scope.ServiceProvider.GetService<IMyService>();
service?.Execute();
}
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}
Register your background worker in your service collection:
services.AddHostedService<ShellBackgroundWorker>();
Running the Sample App
The samples/CShells.Workbench project demonstrates a multi-tenant payment platform:
cd samples/CShells.Workbench
dotnet run
Then access (actual ports depend on your Kestrel/HTTPS dev cert setup):
https://localhost:5001/- Default tenant (Basic tier - Stripe + Email)https://localhost:5001/acme- Acme Corp (Premium tier - PayPal + SMS + Fraud Detection)https://localhost:5001/contoso- Contoso Ltd (Enterprise tier - Stripe + Multi-channel + Fraud + Reporting)https://localhost:5001/swagger- Swagger UI for all endpoints
See the Workbench README for detailed feature descriptions and API examples.
License
MIT License - see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- CShells.Abstractions (>= 0.0.5)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
-
net8.0
- CShells.Abstractions (>= 0.0.5)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.Extensions.Configuration (>= 9.0.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.11)
- Microsoft.Extensions.DependencyInjection (>= 9.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Logging (>= 9.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.11)
-
net9.0
- CShells.Abstractions (>= 0.0.5)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.Extensions.Configuration (>= 9.0.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.11)
- Microsoft.Extensions.DependencyInjection (>= 9.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.11)
- Microsoft.Extensions.Logging (>= 9.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.11)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on CShells:
| Package | Downloads |
|---|---|
|
CShells.AspNetCore
ASP.NET Core integration for CShells. Provides middleware and extensions for shell/tenant resolution based on HTTP context, including host-based and route-based strategies for modular multi-tenant applications. |
GitHub repositories
This package is not used by any popular GitHub repositories.