DecoratR 1.4.0

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

NuGet NuGet Build Status

DecoratR

Intuitive .NET library for implementing the Decorator pattern with Microsoft's Dependency Injection container. DecoratR provides a fluent API to chain decorators around your services, enabling cross-cutting concerns like logging, caching, retry logic, and more.

Table of Contents

Installation

dotnet add package DecoratR

Quick Start

using DecoratR;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

// Basic decoration
services.Decorate<IUserService>()
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<UserService>()
        .Apply();

var provider = services.BuildServiceProvider();
var userService = provider.GetService<IUserService>();
// Result: LoggingDecorator -> CacheDecorator -> UserService

Core Concepts

Decorator Chain Order

Decorators are applied in the order they are defined. The last decorator added should be your base implementation:

services.Decorate<IService>()
        .With<FirstDecorator>()    // Outermost decorator
        .Then<SecondDecorator>()   // Middle decorator
        .Then<BaseService>()       // Base implementation (innermost)
        .Apply();

Constructor Requirements

Decorators (except the base implementation) must have a constructor that accepts the service type as the first parameter:

public class LoggingDecorator(IService inner) : IService
{
    public string Execute() => $"Log({inner.Execute()})";
}

Basic Usage

Simple Decorator Chain

using System.Collections.Concurrent;

// Service interface
public interface IOrderService
{
    Task<Order> GetOrderAsync(int orderId);
}

// Logging decorator
public class LoggingDecorator(IOrderService inner) : IOrderService
{
    public async Task<Order> GetOrderAsync(int orderId)
    {
        Console.WriteLine($"Getting order {orderId}");
        return await inner.GetOrderAsync(orderId);
    }
}

// Cache decorator
public class CacheDecorator(IOrderService inner) : IOrderService
{
    private static readonly ConcurrentDictionary<int, Order> _cache = new();

    public async Task<Order> GetOrderAsync(int orderId)
    {
        if (_cache.TryGetValue(orderId, out var cachedOrder))
            return cachedOrder;

        var order = await inner.GetOrderAsync(orderId);
        _cache.TryAdd(orderId, order);
        return order;
    }
}

// Configuration
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<OrderService>()
        .AsScoped()
        .Apply();

Conditional Decoration

Apply decorators based on runtime conditions:

services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .ThenIf<RetryDecorator>(env.IsDevelopment())  // Only in development
        .ThenIf<CacheDecorator>(enableCaching)        // Based on configuration
        .Then<OrderService>()
        .Apply();

Keyed Services

Note: Keyed services require .NET 8.0 or later. This feature is not available when targeting .NET 6.0 or 7.0.

DecoratR fully supports .NET 8+ keyed services, allowing you to create different decorator chains for the same service type:

// Different configurations for different contexts
services.Decorate<IOrderService>("internal")
        .With<LoggingDecorator>()
        .Then<OrderService>()
        .Apply();

services.Decorate<IOrderService>("external")
        .With<LoggingDecorator>()
        .Then<RateLimitingDecorator>()
        .Then<SecurityDecorator>()
        .Then<OrderService>()
        .Apply();

services.Decorate<IOrderService>("cached")
        .With<CacheDecorator>()
        .Then<OrderService>()
        .AsSingleton()
        .Apply();

// Usage
var internalService = provider.GetRequiredKeyedService<IOrderService>("internal");
var externalService = provider.GetRequiredKeyedService<IOrderService>("external");
var cachedService = provider.GetRequiredKeyedService<IOrderService>("cached");

Complex Keys

You can use any object as a key, including anonymous objects:

var productionKey = new { Environment = "Production", Version = "v2" };

services.Decorate<IOrderService>(productionKey)
        .With<AuditDecorator>()
        .Then<SecurityDecorator>()
        .Then<OrderService>()
        .Apply();

// Usage
var service = provider.GetRequiredKeyedService<IOrderService>(productionKey);

Custom Factory Methods

For decorators that require complex initialization or dependencies not easily handled by the DI container:

services.AddSingleton<IMetrics, MetricsService>();
services.AddSingleton<IConfiguration, ConfigurationService>();

services.Decorate<IOrderService>()
        .With((serviceProvider, inner) =>
            new MetricsDecorator(
                inner,
                serviceProvider.GetRequiredService<IMetrics>(),
                serviceProvider.GetRequiredService<IConfiguration>().GetValue<string>("MetricsPrefix")))
        .Then<OrderService>()
        .Apply();

Advanced Factory Examples

// Conditional factory with complex logic
services.Decorate<IOrderService>()
        .WithIf(enableAdvancedMetrics, (sp, inner) =>
        {
            var config = sp.GetRequiredService<IConfiguration>();
            var metrics = sp.GetRequiredService<IMetrics>();
            var logger = sp.GetRequiredService<ILogger<AdvancedMetricsDecorator>>();

            return new AdvancedMetricsDecorator(inner, metrics, config, logger);
        })
        .Then<OrderService>()
        .Apply();

// Factory for base implementation
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then((serviceProvider, _) =>
        {
            var connectionString = serviceProvider.GetRequiredService<IConfiguration>()
                                                  .GetConnectionString("DefaultConnection");
            
            return new DatabaseOrderService(connectionString);
        })
        .Apply();

Generic Decorators

DecoratR supports generic decorators with multiple type parameters:

// Generic decorator with one type parameter
public class CacheDecorator<T>(IService<T> inner) : IService<T>
{
    private static readonly Dictionary<string, T> _cache = new();

    public async Task<T> GetAsync(string key)
    {
        if (_cache.TryGetValue(key, out var cachedValue))
            return cachedValue;

        var value = await inner.GetAsync(key);
        _cache[key] = value;
        return value;
    }
}

// Multiple generic parameters
public class TransformDecorator<TInput, TOutput>(ITransformService<TInput, TOutput> inner) 
    : ITransformService<TInput, TOutput>
{
    public async Task<TOutput> TransformAsync(TInput input)
    {
        Console.WriteLine($"Transforming {typeof(TInput).Name} to {typeof(TOutput).Name}");
        return await inner.TransformAsync(input);
    }
}

// Configuration
services.Decorate<IService<string>>()
        .With<CacheDecorator<string>>()
        .Then<StringService>()
        .Apply();

services.Decorate<ITransformService<User, UserDto>>()
        .With<TransformDecorator<User, UserDto>>()
        .Then<UserTransformService>()
        .Apply();

Lifetime Management

Control the lifetime of your decorated services:

// Singleton
services.Decorate<IOrderService>()
        .With<CacheDecorator>()
        .Then<OrderService>()
        .AsSingleton()
        .Apply();

// Scoped
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then<OrderService>()
        .AsScoped()
        .Apply();

// Transient (default)
services.Decorate<IOrderService>()
        .With<RetryDecorator>()
        .Then<OrderService>()
        .AsTransient()  // Optional, as it's the default
        .Apply();

// Custom lifetime
services.Decorate<IOrderService>()
        .With<MetricsDecorator>()
        .Then<OrderService>()
        .WithLifetime(ServiceLifetime.Scoped)
        .Apply();

Best Practices

1. Decorator Ordering

Consider the logical flow of your decorators:

// Good: Logical order from outside to inside
services.Decorate<IService>()
        .With<SecurityDecorator>()      // Authentication/Authorization first
        .Then<RateLimitingDecorator>()  // Rate limiting after security
        .Then<LoggingDecorator>()       // Logging after rate limiting
        .Then<MetricsDecorator>()       // Metrics collection
        .Then<CacheDecorator>()         // Cache closest to data source
        .Then<BaseService>()            // Base implementation
        .Apply();

2. Error Handling

Implement proper error handling in decorators:

public class ErrorHandlingDecorator(IService inner, ILogger<ErrorHandlingDecorator> logger) : IService
{
    public async Task<Result> ExecuteAsync(Request request)
    {
        try
        {
            return await inner.ExecuteAsync(request);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error executing request {RequestId}", request.Id);
            throw;
        }
    }
}

3. Performance Considerations

Be mindful of decorator overhead:

// Use conditional decoration for expensive operations
var enableDetailedLogging = config.GetValue<bool>("Logging:Detailed");

services.Decorate<IService>()
        .WithIf<DetailedLoggingDecorator>(enableDetailedLogging)
        .Then<BaseService>()
        .Apply();

4. Testing

Decorators are easy to test in isolation:

[Test]
public void LoggingDecorator_ShouldLogExecution()
{
    // Arrange
    var mockInner = new Mock<IService>();
    var mockLogger = new Mock<ILogger<LoggingDecorator>>();
    var decorator = new LoggingDecorator(mockInner.Object, mockLogger.Object);

    // Act
    decorator.Execute();

    // Assert
    mockLogger.Verify(x => x.LogInformation(It.IsAny<string>()), Times.Once);
    mockInner.Verify(x => x.Execute(), Times.Once);
}

API Reference

Extension Methods

  • Decorate<TService>() - Begin decoration for a service type
  • Decorate<TService>(object serviceKey) - Begin decoration for a keyed service

IDecorationBuilder Methods

  • Then<TDecorator>() - Add a decorator to the chain
  • With<TDecorator>() - Alias for Then<TDecorator>()
  • Then(Func<IServiceProvider, TService, TService> factory) - Add decorator via factory
  • ThenIf<TDecorator>(bool condition) - Conditionally add decorator
  • WithIf<TDecorator>(bool condition) - Alias for ThenIf<TDecorator>(bool condition)
  • ThenIf(bool condition, Func<IServiceProvider, TService, TService> factory) - Conditionally add decorator via factory
  • WithLifetime(ServiceLifetime lifetime) - Set service lifetime
  • AsSingleton() - Set lifetime to Singleton
  • AsScoped() - Set lifetime to Scoped
  • AsTransient() - Set lifetime to Transient
  • Apply() - Apply the decoration configuration

Requirements

  • .NET 6.0 or later
  • Microsoft.Extensions.DependencyInjection 6.0.0 or later

Note: Keyed services are only available in .NET 8.0 or later. If you're using .NET 6.0 or 7.0, you can only use the regular (non-keyed) decoration features.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests on the GitHub repository.

License

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

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on DecoratR:

Package Downloads
Automation.Playwright

封装的 Playwright 自动化库,便于在应用中通过依赖注入和托管服务使用 Playwright。

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.4.0 401 2/13/2026
1.3.0 1,344 12/12/2025
1.1.0 1,686 8/22/2025
1.0.0 613 7/24/2025