Qualia.Decorators
0.1.0
See the version list below for details.
dotnet add package Qualia.Decorators --version 0.1.0
NuGet\Install-Package Qualia.Decorators -Version 0.1.0
<PackageReference Include="Qualia.Decorators" Version="0.1.0" />
paket add Qualia.Decorators --version 0.1.0
#r "nuget: Qualia.Decorators, 0.1.0"
// Install Qualia.Decorators as a Cake Addin #addin nuget:?package=Qualia.Decorators&version=0.1.0 // Install Qualia.Decorators as a Cake Tool #tool nuget:?package=Qualia.Decorators&version=0.1.0
Qualia.Decorators Library
The Decorators library is a powerful tool for C# developers that allows you to add additional behavior to your classes and methods using attributes. This library uses the decorator pattern to wrap your classes and methods with additional functionality without modifying their implementation.
The Decorate attribute can be used to intercept a method call, and add behavior before and/or after the actual call. This is done without modifying the original method code.
Features
- DecorateAttribute: This attribute can be applied to classes or methods. It takes a Type parameter that represents the decorator behavior to be applied.
- IDecoratorBehavior: This interface defines the decorator behavior. You can implement this interface to create your own custom decorators.
- DecorateIgnoreAttribute: This attribute can be applied to methods. It allows you to ignore certain class decorators for specific methods.
- ServiceCollectionExtensions: This class provides extension methods for IServiceCollection to easily add decorators to your services in an ASP.NET Core application.
Usage
Here is an example of how to use the Decorators library:
// Define your decorator behavior
public class MyDecoratorBehavior : IDecoratorBehavior
{
public object? Invoke<TDecorated>(TDecorated decorated, MethodInfo targetMethod, object?[]? args)
{
// Add your decorator behavior here
// do things before the method call
//delegate the call
var result = targetMethod.Invoke(decorated, args);
//do things after the method call
}
}
// Apply the decorator to a class or method
[Decorate(typeof(MyDecoratorBehavior))]
public class MyClass
{
//MyDecoratorBehavior wraps this method
public void MyMethod()
{
// Your method implementation
}
[Decorate(typeof(FooDecoratorBehavior))]
//both MyDecoratorBehavior and FooDecoratorBehavior wrap this method
public void MyOtherMethod()
{
// Your method implementation
}
}
// Decorators are nested in the order they are declared.
[Decorate(typeof(FooBehavior))]
[Decorate(typeof(FooBehavior))]
[Decorate(typeof(BarBehavior))]
public class MyClass
{
//client -> foo -> foo -> bar -> MyMethod
public void MyMethod()
{
// Your method implementation
}
[Decorate(typeof(LogBehavior))]
[Decorate(typeof(CacheBehavior))]
//client -> foo -> foo -> bar -> log -> cache -> MyOtherMethod
public void MyOtherMethod()
{
// Your method implementation
}
}
//you can ignore class decorators
[Decorate(typeof(FooBehavior))]
[Decorate(typeof(BarBehavior))]
[Decorate(typeof(BarBehavior), "barName")]
public class MyClass
{
//all class decorators apply
public void MyMethod()
{
// Your method implementation
}
[DecorateIgnore] //ignore all class decorators (foo, bar, bar)
[Decorate(typeof(LogBehavior))]
//only log applies
public void MyOtherMethod()
{
// Your method implementation
}
[DecorateIgnore(typeof(BarBehavior))] //ignore all class bar decorators
[Decorate(typeof(LogBehavior))]
//foo and log apply
public void MyThirdMethod()
{
// Your method implementation
}
[DecorateIgnore("barName")] //ignore named decorator "barName"
//foo and bar apply
public void MyLastMethod()
{
// Your method implementation
}
}
// To enable the attributes you need to add MyClass as a service to the service collection
// and then call services.UseDecorators method. MyClass must be added behind an interface.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyClass, MyClass>();
services.UseDecorators();
}
Please note that the Decorators library uses reflection and should be used judiciously considering its impact on performance.
Example of custom decorator behavior
To extend the library, simply implement IDecoratorBehavior, and use the new class with the decorate attribute:
public class Memoize : IDecoratorBehavior
{
private ILogger<Memoize> _logger;
private readonly ConcurrentDictionary<string, object?> _cache = new();
//you can inject services to your constructor
public Memoize(ILogger<Memoize> logger)
{
_logger = logger;
}
public object? Invoke<TDecorated>(TDecorated decorated, MethodInfo targetMethod, object?[]? args)
{
try
{
//get a hash of the target methods arguments to use as a cache key
var key = GenerateCacheKey(targetMethod, args);
//call the target method if key not in cache, and cache the key & result in memory
//or just return the result if the key is in the cache
var result = _cache.GetOrAdd(key, _ => targetMethod.Invoke(decorated, args));
return result;
}
catch (TargetInvocationException ex)
{
_logger?.LogError(ex.InnerException ?? ex,
"Error during invocation of {decoratedClass}.{methodName}", typeof(TDecorated), targetMethod?.Name);
throw ex.InnerException ?? ex;
}
}
private static string GenerateCacheKey(MethodInfo targetMethod, object?[]? args)
{
var serializedArgs = JsonSerializer.Serialize(args);
byte[] bytes = Encoding.UTF8.GetBytes(serializedArgs);
byte[] hashBytes = SHA1.HashData(bytes);
return $"{targetMethod.Name}_{BitConverter.ToString(hashBytes).Replace("-", "")}";
}
}
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. |
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.