Aftermath 1.0.1
dotnet add package Aftermath --version 1.0.1
NuGet\Install-Package Aftermath -Version 1.0.1
<PackageReference Include="Aftermath" Version="1.0.1" />
<PackageVersion Include="Aftermath" Version="1.0.1" />
<PackageReference Include="Aftermath" />
paket add Aftermath --version 1.0.1
#r "nuget: Aftermath, 1.0.1"
#:package Aftermath@1.0.1
#addin nuget:?package=Aftermath&version=1.0.1
#tool nuget:?package=Aftermath&version=1.0.1
Aftermath
Aftermath is a powerful .NET library that allows you to declare post-execution hooks for methods without modifying the original method implementation. It implements aspect-oriented programming (AOP) principles to handle cross-cutting concerns in a clean, maintainable way.
Features
- 🔄 Non-invasive: Add behavior to methods without changing their implementation
- 🧩 Interface-based: Works with interfaces and dependency injection
- 🔌 Easy integration: Seamless integration with ASP.NET Core dependency injection
- ⚡ Async-aware: Full support for async methods (Task, Task<T>, ValueTask, ValueTask<T>)
- 🔧 Highly configurable: Extensive options for controlling hook behavior
- 🚀 Performance-conscious: Skip hooks in release mode, timeout control
- 📊 Execution context: Rich information about method execution passed to hooks
- 🪝 Advanced hook features: Parameter mapping, return value access, error policies
- 💉 DI-friendly: Automatic parameter resolution from services
Installation
dotnet add package Aftermath
Quick Start
- Define your interface with hooks:
public interface IUserService : IHookable
{
[CallAfter(typeof(LoggingService), nameof(LoggingService.LogUserCreation))]
Task<User> CreateUserAsync(string username, string email);
}
- Register services in your startup:
services.AddAftermath();
services.AddHookedScoped<IUserService, UserService>();
- Implement your service normally:
public class UserService : IUserService
{
public virtual async Task<User> CreateUserAsync(string username, string email)
{
// Your implementation here
return new User { Username = username, Email = email };
}
}
- Create your hook method:
public class LoggingService
{
public void LogUserCreation(string username, User user)
{
Console.WriteLine($"User created: {username} with ID {user.Id}");
}
}
How It Works
Aftermath uses dynamic proxies to intercept method calls and execute hooks after the original method completes. When you call a method on a proxied interface, Aftermath:
- Executes the original method implementation
- Captures execution details (parameters, return value, execution time, etc.)
- Invokes all defined hook methods with the execution context
Authentication Example
The example in this repository demonstrates how to use Aftermath for cross-cutting concerns in an authentication system:
public interface IUserService : IHookable
{
[CallAfter(typeof(IAuthenticationLogger), nameof(IAuthenticationLogger.LogAuthenticationAttemptAsync))]
[CallAfter(typeof(SecurityAuditService), nameof(SecurityAuditService.RecordAuthenticationAttempt))]
[CallAfter(typeof(BruteForceDetector), nameof(BruteForceDetector.CheckForBruteForceAttack))]
[MapParameter("username", "username")]
[MapReturnValue("success")]
Task<bool> AuthenticateAsync(string username, string password);
}
In this example:
- The
AuthenticateAsync
method performs authentication logic - After execution, Aftermath automatically:
- Logs the authentication attempt
- Records the attempt in a security audit
- Checks for potential brute force attacks
All without modifying the authentication method itself!
Running the Example
// Configure services
var services = new ServiceCollection();
services.AddAftermath();
services.AddHookedScoped<IUserService, UserService>();
services.AddScoped<IUserManager, UserManager>();
services.AddSingleton<IAuthenticationLogger, AuthenticationLogger>();
services.AddSingleton<SecurityAuditService>();
services.AddSingleton<BruteForceDetector>();
// Build provider and get service
var serviceProvider = services.BuildServiceProvider();
var userService = serviceProvider.GetRequiredService<IUserService>();
// Use the service normally
var result = await userService.AuthenticateAsync("john.doe", "password");
Advanced Features
Parameter Mapping
Map source method parameters to hook method parameters:
[CallAfter(typeof(LoggingService), nameof(LoggingService.LogAction))]
[MapParameter("userId", "user")]
public void UpdateUser(int userId, UserUpdateDto data)
Injecting Custom Values
Inject custom values into hook parameters:
[CallAfter(typeof(AuditService), nameof(AuditService.RecordAction))]
[InjectParameter("actionType", "USER_UPDATE")]
public void UpdateUser(int userId, UserUpdateDto data)
Conditional Execution
Execute hooks conditionally:
[CallAfter(typeof(NotificationService), nameof(NotificationService.SendPasswordChangeEmail))]
[ExecuteWhen(nameof(ShouldNotifyPasswordChange))]
public void ChangePassword(int userId, string newPassword)
private bool ShouldNotifyPasswordChange(MethodExecutionContext context)
{
return context.GetParameterValue<bool>("sendNotification");
}
Error Handling
Control how hook errors are handled:
// Continue executing other hooks even if this one fails
[CallAfter(typeof(LoggingService), nameof(LoggingService.LogAction), ContinueOnError = true)]
// Stop executing hooks if this one fails
[CallAfter(typeof(CriticalService), nameof(CriticalService.ProcessData), ContinueOnError = false)]
Hook Execution Order
Control the order of hook execution:
[CallAfter(typeof(ValidationService), nameof(ValidationService.ValidateResult), Order = 1)]
[CallAfter(typeof(LoggingService), nameof(LoggingService.LogOperation), Order = 2)]
Performance Optimization
Skip hooks in release mode:
[SkipHooksInRelease]
public Task<DataResult> HeavyOperationAsync()
Configuration
Customize Aftermath behavior:
services.AddAftermath(options =>
{
// Auto-create instances of hook target types not in DI
options.AutoCreateInstancesNotInDI = true;
// Auto-resolve parameters from DI when possible
options.AutoResolveParametersFromDI = true;
// Enable verbose logging
options.VerboseLogging = true;
// Set hook execution timeout
options.HookExecutionTimeoutMs = 5000; // 5 seconds
// Set global error policy
options.GlobalErrorPolicy = HookErrorPolicy.ContinueWithNextHook;
});
Performance Considerations
- Use
[SkipHooksInRelease]
for performance-critical methods - Set appropriate timeout values
- Consider the overhead of proxying and intercepting
- For very high-performance scenarios, hooks may add non-trivial overhead
License
MIT License
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 was computed. 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 was computed. 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. |
-
net8.0
- Castle.Core (>= 5.1.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 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.