Dosaic.Hosting.Abstractions 1.2.8

dotnet add package Dosaic.Hosting.Abstractions --version 1.2.8
                    
NuGet\Install-Package Dosaic.Hosting.Abstractions -Version 1.2.8
                    
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="Dosaic.Hosting.Abstractions" Version="1.2.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dosaic.Hosting.Abstractions" Version="1.2.8" />
                    
Directory.Packages.props
<PackageReference Include="Dosaic.Hosting.Abstractions" />
                    
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 Dosaic.Hosting.Abstractions --version 1.2.8
                    
#r "nuget: Dosaic.Hosting.Abstractions, 1.2.8"
                    
#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 Dosaic.Hosting.Abstractions@1.2.8
                    
#: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=Dosaic.Hosting.Abstractions&version=1.2.8
                    
Install as a Cake Addin
#tool nuget:?package=Dosaic.Hosting.Abstractions&version=1.2.8
                    
Install as a Cake Tool

Dosaic.Hosting.Abstractions

Dosaic.Hosting.Abstractions is the core abstraction/interface package that allows .NET developers to build their own Dosaic plugins. Every plugin in the Dosaic ecosystem depends on this package.

Installation

dotnet add package Dosaic.Hosting.Abstractions

or add as a package reference to your .csproj:

<PackageReference Include="Dosaic.Hosting.Abstractions" Version="" />

Usage

Creating a Plugin

A plugin is any public, non-abstract class that implements one or more of the plugin interfaces below. The source generator in Dosaic.Hosting.Generator automatically discovers all such classes at compile time — no runtime reflection or manual registration needed.

using Dosaic.Hosting.Abstractions.Plugins;
using Microsoft.Extensions.DependencyInjection;

namespace MyCompany.MyPlugin
{
    public class MyPlugin : IPluginServiceConfiguration, IPluginApplicationConfiguration
    {
        public void ConfigureServices(IServiceCollection serviceCollection)
        {
            serviceCollection.AddTransient<IMyService, MyService>();
        }

        public void ConfigureApplication(IApplicationBuilder applicationBuilder)
        {
            applicationBuilder.UseMyMiddleware();
        }
    }
}

Plugin Interfaces

All plugin interfaces live in Dosaic.Hosting.Abstractions.Plugins and extend IPluginActivateable.

Interface Method Purpose
IPluginActivateable Marker interface — all plugins must implement this (directly or via one of the interfaces below)
IPluginServiceConfiguration ConfigureServices(IServiceCollection) DI registrations, called during the service-configuration phase
IPluginApplicationConfiguration ConfigureApplication(IApplicationBuilder) Middleware pipeline setup, called after Build()
IPluginEndpointsConfiguration ConfigureEndpoints(IEndpointRouteBuilder, IServiceProvider) Minimal API endpoint registration
IPluginHealthChecksConfiguration ConfigureHealthChecks(IHealthChecksBuilder) Custom health check registration
IPluginControllerConfiguration ConfigureControllers(IMvcBuilder) MVC/controller configuration
IPluginConfigurator Marker for sub-configurator objects injected as IPluginConfigurator[] collections into plugins

Plugin execution order

Plugin namespace Execution order
Dosaic.* namespace First (sbyte.MinValue)
Third-party plugins Middle (0)
Host assembly plugins Last (sbyte.MaxValue)

IPluginServiceConfiguration

Called during service-collection setup. Use this for DI registrations.

public class MyPlugin : IPluginServiceConfiguration
{
    public void ConfigureServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddTransient<IMyService, MyService>();
        serviceCollection.AddSingleton<MyOptions>();
    }
}

IPluginApplicationConfiguration

Called after WebApplication.Build(). Use this for UseXyz() pipeline calls.

public class MyPlugin : IPluginApplicationConfiguration
{
    public void ConfigureApplication(IApplicationBuilder applicationBuilder)
    {
        applicationBuilder.UseRouting();
        applicationBuilder.UseAuthentication();
    }
}

IPluginEndpointsConfiguration

Registers minimal API endpoints. Called during endpoint routing setup.

public class MyPlugin : IPluginEndpointsConfiguration
{
    public void ConfigureEndpoints(IEndpointRouteBuilder endpointRouteBuilder, IServiceProvider serviceProvider)
    {
        endpointRouteBuilder.MapGet("/hello", () => "Hello, World!")
            .RequireAuthorization();
    }
}

IPluginHealthChecksConfiguration

Registers health checks programmatically. See also the health check attributes for declarative registration.

public class MyPlugin : IPluginHealthChecksConfiguration
{
    public void ConfigureHealthChecks(IHealthChecksBuilder healthChecksBuilder)
    {
        healthChecksBuilder.AddUrlGroup(
            new Uri("https://my-dependency/health"),
            "my-dependency",
            HealthStatus.Unhealthy,
            new[] { HealthCheckTag.Readiness });
    }
}

IPluginControllerConfiguration

Configures the MVC builder (e.g. options, formatters, assemblies).

public class MyPlugin : IPluginControllerConfiguration
{
    public void ConfigureControllers(IMvcBuilder controllerBuilder)
    {
        controllerBuilder.AddMvcOptions(options => options.RespectBrowserAcceptHeader = true);
        controllerBuilder.AddApplicationPart(typeof(MyPlugin).Assembly);
    }
}

IPluginConfigurator

A sub-configurator is a small, focused configuration object that is automatically collected and injected as an array into plugins that declare a constructor parameter of type IPluginConfigurator[] (or IEnumerable<IPluginConfigurator>).

// Sub-configurator defined anywhere in the application
public class MyFeatureConfigurator : IPluginConfigurator
{
    public string ConnectionString { get; set; } = "...";
}

// Plugin that receives all configurators
public class MyPlugin : IPluginServiceConfiguration
{
    private readonly MyFeatureConfigurator[] _configurators;

    public MyPlugin(MyFeatureConfigurator[] configurators)
    {
        _configurators = configurators;
    }

    public void ConfigureServices(IServiceCollection serviceCollection) { ... }
}

Attributes

[Configuration]

Decorating a class with [Configuration("section")] causes the web host to automatically bind the class to the corresponding section in appsettings.* files or environment variables. The bound instance is then injectable as a constructor parameter in any plugin.

# appsettings.yaml
myFeature:
  connectionString: "Server=localhost"
  timeout: 30
using Dosaic.Hosting.Abstractions.Attributes;

[Configuration("myFeature")]
public class MyFeatureConfiguration
{
    public string ConnectionString { get; set; }
    public int Timeout { get; set; }
}

// Consume in any plugin via constructor injection
public class MyPlugin : IPluginServiceConfiguration
{
    private readonly MyFeatureConfiguration _config;

    public MyPlugin(MyFeatureConfiguration config)
    {
        _config = config;
    }

    public void ConfigureServices(IServiceCollection serviceCollection) { ... }
}

Nested sections are supported using : as the delimiter:

[Configuration("database:primary")]
public class PrimaryDbConfig { ... }

[Middleware]

Marks an ApiMiddleware subclass for automatic registration in the middleware pipeline. The optional order parameter controls execution order (default: int.MaxValue — runs last). Lower values run earlier.

using Dosaic.Hosting.Abstractions.Attributes;

[Middleware(order: 100)]
public class MyMiddleware : ApiMiddleware
{
    public MyMiddleware(RequestDelegate next, IDateTimeProvider dateTimeProvider)
        : base(next, dateTimeProvider) { }

    public override async Task Invoke(HttpContext context)
    {
        // before
        await Next.Invoke(context);
        // after
    }
}

Built-in middlewares and their order:

Middleware Order Behaviour
ExceptionMiddleware int.MinValue (first) Catches all exceptions; maps DosaicException subtypes to HTTP status codes
EnrichRequestMetricsMiddleware int.MaxValue (last) Adds azp claim as a metrics tag on every request
RequestContentLengthLimitMiddleware int.MaxValue (last) Returns 413 when Content-Length exceeds the Kestrel limit

[ReadinessCheckAttribute]

Registers an IHealthCheck implementation as a readiness health check (tags it with HealthCheckTag.Readiness).

using Dosaic.Hosting.Abstractions.Attributes;

[ReadinessCheck("database")]
public class DatabaseHealthCheck : IHealthCheck
{
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        // check connectivity ...
        return HealthCheckResult.Healthy("Database is reachable.");
    }
}

[LivenessCheckAttribute]

Registers an IHealthCheck implementation as a liveness health check (tags it with HealthCheckTag.Liveness).

[LivenessCheck("application")]
public class ApplicationLivenessCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
        => Task.FromResult(HealthCheckResult.Healthy());
}

Both attributes can be combined on the same class, and additional tags can be passed via the overloaded constructors:

[ReadinessCheck("kafka")]
[LivenessCheck("kafka")]
public class KafkaHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        // check Kafka connectivity
        return Task.FromResult(HealthCheckResult.Healthy("Kafka is available."));
    }
}

[YamlTypeConverterAttribute]

Attaches a custom YAML type converter to a class or struct so that it is used during SerializationExtensions.Serialize / Deserialize calls with SerializationMethod.Yaml. The converter type must implement IYamlConverter.

using Dosaic.Hosting.Abstractions.Attributes;

[YamlTypeConverter(typeof(MyTypeConverter))]
public class MySpecialType { ... }

public class MyTypeConverter : IYamlConverter
{
    public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { ... }
    public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) { ... }
}

HealthCheckTag

HealthCheckTag is a validated value object (via Vogen) with two predefined instances:

Instance Value
HealthCheckTag.Readiness "readiness"
HealthCheckTag.Liveness "liveness"

Custom tags are supported:

var tag = HealthCheckTag.From("custom-tag");

Exceptions

All domain exceptions extend DosaicException, which carries an HttpStatus property automatically used by ExceptionMiddleware to produce the correct HTTP response.

Exception Default HTTP status
DosaicException 500 Internal Server Error (configurable via constructor)
NotFoundDosaicException 404 Not Found
ConflictDosaicException 409 Conflict
ValidationDosaicException 422 Unprocessable Entity
using Dosaic.Hosting.Abstractions.Exceptions;

// Simple usage
throw new NotFoundDosaicException("Order", orderId);
// → message: "Could not find Order with id '42'"

throw new ConflictDosaicException("An order with this reference already exists.");

throw new ValidationDosaicException("Validation failed", new List<FieldValidationError>
{
    new("email", "must be a valid email address"),
    new("amount", "must be greater than 0")
});

// Throw with a custom HTTP status
throw new DosaicException("Service unavailable.", StatusCodes.Status503ServiceUnavailable);

Error Response Models

ExceptionMiddleware produces JSON responses using these models from Dosaic.Hosting.Abstractions.Middlewares.Models:

// Standard error response
record ErrorResponse(DateTime Timestamp, string Message, string RequestId);

// Validation error response (extends ErrorResponse)
record ValidationErrorResponse : ErrorResponse
{
    IEnumerable<FieldValidationError> ValidationErrors { get; }
}

// Single field validation error
record FieldValidationError(string Field, string ValidationMessage);

IImplementationResolver

IImplementationResolver is the runtime service used internally by the web host to discover and instantiate plugins. It is also injectable into plugins for advanced scenarios.

public interface IImplementationResolver
{
    List<Type> FindTypes();
    List<Assembly> FindAssemblies();
    object ResolveInstance(Type type);
    void ClearInstances();
}

Convenience extensions from ImplementationResolverExtensions:

// Find all types matching a predicate
List<Type> types = resolver.FindTypes(t => t.HasAttribute<MyAttribute>());

// Find and instantiate all types matching a predicate
List<object> instances = resolver.FindAndResolve(t => t.Implements<IMyService>());

// Find and instantiate all implementations of T
List<IMyService> services = resolver.FindAndResolve<IMyService>();

IFactory<T>

IFactory<TService> is a lightweight factory abstraction for resolving services lazily or optionally from the DI container. Register using AddFactory<T>().

using Dosaic.Hosting.Abstractions.DependencyInjection;
using Dosaic.Hosting.Abstractions.Extensions;

// Registration
serviceCollection.AddFactory<IMyService>();

// Injection and usage
public class MyConsumer
{
    private readonly IFactory<IMyService> _factory;

    public MyConsumer(IFactory<IMyService> factory) => _factory = factory;

    public void DoWork()
    {
        var service = _factory.Create();             // throws if not registered
        var serviceOrNull = _factory.CreateOrNull(); // returns null if not registered
    }
}

Metrics

Dosaic uses OpenTelemetry for metrics. The static Metrics class provides typed factory methods that cache instrument instances, preventing duplicate-creation errors.

using Dosaic.Hosting.Abstractions.Metrics;

// Counter
var counter = Metrics.CreateCounter<long>("my_requests_total", "calls", "Total number of requests");
counter.Add(1);
counter.Add(1, new KeyValuePair<string, object>("status", "ok"));

// Histogram
var histogram = Metrics.CreateHistogram<double>("my_request_duration_seconds", "s", "Request duration");
histogram.Record(0.42);

// Observable gauge (value polled by the metrics exporter)
Metrics.CreateObservableGauge("queue_depth", () => GetQueueDepth(), "items", "Current queue depth");

// Observable counter
Metrics.CreateObservableCounter("processed_total", () => GetProcessedCount(), "items", "Total processed");

Access the underlying Meter directly for advanced scenarios:

var upDownCounter = Metrics.Meter.CreateUpDownCounter<int>("active_connections", "connections");

Tracing

DosaicDiagnostic

DosaicDiagnostic.CreateSource() creates an ActivitySource named after the calling class's fully-qualified name using the call stack. Call it from a static field initializer.

using Dosaic.Hosting.Abstractions;
using System.Diagnostics;

public class MyService
{
    private static readonly ActivitySource _activitySource = DosaicDiagnostic.CreateSource();

    public async Task DoWorkAsync()
    {
        using var activity = _activitySource.StartActivity("DoWork");
        // ... work ...
    }
}

Constants:

Constant Value Purpose
DosaicDiagnostic.DosaicActivityPrefix "Dosaic." Prefix for all Dosaic activity sources
DosaicDiagnostic.DosaicAllActivities "Dosaic.*" Wildcard for subscribing to all Dosaic traces in OpenTelemetry

TracingExtensions

using Dosaic.Hosting.Abstractions.Extensions;

// Wraps an async call and auto-sets Ok/Error activity status
var result = await _activitySource.TrackStatusAsync(async activity =>
{
    activity?.SetTag("input", input);
    return await _service.ProcessAsync(input);
});

// Set tags in bulk with an optional prefix
activity.SetTags(new Dictionary<string, string> { ["key"] = "value" }, prefix: "app.");

// Manually set status
activity.SetOkStatus();
activity.SetErrorStatus(exception);

Serialization

SerializationExtensions provides uniform JSON and YAML serialization.

using Dosaic.Hosting.Abstractions.Extensions;

var obj = new MyDto { Name = "hello" };

// JSON (default)
string json = obj.Serialize();
MyDto dto = json.Deserialize<MyDto>();

// YAML
string yaml = obj.Serialize(SerializationMethod.Yaml);
MyDto dto2 = yaml.Deserialize<MyDto>(SerializationMethod.Yaml);

// Access the default JsonSerializerOptions (camelCase, enums as strings, etc.)
var options = SerializationExtensions.DefaultOptions;

IKindSpecifier — Polymorphic deserialization

Implementing IKindSpecifier on an interface enables discriminated-union deserialization where the concrete type is selected based on a Kind property in the JSON/YAML payload.

public interface IShape : IKindSpecifier { }

public class Circle : IShape
{
    public string Kind => "circle";
    public double Radius { get; set; }
}

public class Rectangle : IShape
{
    public string Kind => "rectangle";
    public double Width { get; set; }
    public double Height { get; set; }
}

// Deserializes to Circle or Rectangle depending on the "kind" field
IShape shape = """{"kind":"circle","radius":5}""".Deserialize<IShape>();

Utility Extensions

ObjectExtensions — DeepPatch

Applies non-null property values from a patch object onto a target, with configurable behaviour for nested objects and lists.

using Dosaic.Hosting.Abstractions.Extensions;

var original = new MyEntity { Name = "Old", Tags = new List<string> { "a" } };
var patch    = new MyEntity { Name = "New", Tags = new List<string> { "b" } };

original.DeepPatch(patch);
// original.Name == "New", original.Tags == ["a", "b"]   (new items are merged)

original.DeepPatch(patch, PatchMode.OverwriteLists);
// original.Tags == ["b"]                                 (list is replaced)

PatchMode flags: Full, IgnoreLists, IgnoreObjects, OverwriteLists.

StringExtensions

"MyPropertyName".ToSnakeCase();     // "my_property_name"
"hello world".ToUrlEncoded();       // "hello%20world"
"hello%20world".FromUrlEncoded();   // "hello world"

ConfigurationExtensions

// Bind a config section to a new instance of T
var opts = configuration.BindToSection<MyOptions>("myFeature");

TypeExtensions

typeof(MyClass).Implements<IMyInterface>()   // true / false
typeof(MyClass).HasAttribute<MyAttribute>()  // true / false
typeof(MyClass).GetAttribute<MyAttribute>()  // attribute instance or null
typeof(MyClass).CanBeInstantiated()          // !IsAbstract && !IsInterface
typeof(MyClass).GetNormalizedName()          // human-readable generic type name

EnumerableExtensions

items.ForEach(item => Console.WriteLine(item));

EnumExtensions

PatchMode.IgnoreLists.IsFlagSet(PatchMode.IgnoreLists); // true

GlobalStatusCodeOptions

Controls which HTTP status codes are automatically rewritten to the standard ErrorResponse JSON format by ExceptionMiddleware. Default codes: 401, 403, 404, 406, 415, 500.

// Customise in a plugin's ConfigureServices:
serviceCollection.Configure<GlobalStatusCodeOptions>(opts =>
{
    opts.Add(HttpStatusCode.ServiceUnavailable);
    opts.Remove(HttpStatusCode.NotFound);
    opts.Clear(); // remove all defaults
});

ApiMiddleware

ApiMiddleware is the abstract base class for all Dosaic middlewares. Subclass it and decorate with [Middleware] for automatic registration in the pipeline.

using Dosaic.Hosting.Abstractions;
using Dosaic.Hosting.Abstractions.Attributes;
using Chronos.Abstractions;

[Middleware(order: 50)]
public class CorrelationIdMiddleware : ApiMiddleware
{
    public CorrelationIdMiddleware(RequestDelegate next, IDateTimeProvider dateTimeProvider)
        : base(next, dateTimeProvider) { }

    public override async Task Invoke(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue("X-Correlation-Id", out _))
            context.Response.Headers.Append("X-Correlation-Id", Guid.NewGuid().ToString());

        await Next.Invoke(context);
    }
}

Helper methods available inside ApiMiddleware:

// Write a typed JSON response
await WriteResponse(context, StatusCodes.Status200OK, new { ok = true });

// Write the standard ErrorResponse JSON
await WriteDefaultResponse(context, StatusCodes.Status400BadRequest, "Custom message");
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 (25)

Showing the top 5 NuGet packages that depend on Dosaic.Hosting.Abstractions:

Package Downloads
Dosaic.Plugins.Persistence.MongoDb

A plugin-first dotnet framework for rapidly building anything hosted in the web.

Dosaic.Plugins.Authorization.Keycloak

A plugin-first dotnet framework for rapidly building anything hosted in the web.

Dosaic.Testing.NUnit

A plugin-first dotnet framework for rapidly building anything hosted in the web.

Dosaic.Plugins.Persistence.S3

A plugin-first dotnet framework for rapidly building anything hosted in the web.

Dosaic.Plugins.Management.Unleash

A plugin-first dotnet framework for rapidly building anything hosted in the web.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.8 152 3/9/2026
1.2.7 183 3/4/2026
1.2.6 194 2/19/2026
1.2.5 175 2/17/2026
1.2.4 212 2/13/2026
1.2.3 215 1/27/2026
1.2.2 385 12/16/2025
1.2.1 380 12/16/2025
1.2.0 523 12/11/2025
1.1.21 596 12/10/2025
1.1.20 565 11/18/2025
1.1.19 456 11/11/2025
1.1.18 372 10/14/2025
1.1.17 355 10/1/2025
1.1.16 392 9/25/2025
1.1.15 371 9/24/2025
1.1.14 393 9/24/2025
1.1.13 362 9/24/2025
1.1.12 445 9/16/2025
1.1.11 317 7/18/2025
Loading failed