Aftermath 1.0.1

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

Aftermath

NuGet Build Status License

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

  1. Define your interface with hooks:
public interface IUserService : IHookable
{
    [CallAfter(typeof(LoggingService), nameof(LoggingService.LogUserCreation))]
    Task<User> CreateUserAsync(string username, string email);
}
  1. Register services in your startup:
services.AddAftermath();
services.AddHookedScoped<IUserService, UserService>();
  1. 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 };
    }
}
  1. 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:

  1. Executes the original method implementation
  2. Captures execution details (parameters, return value, execution time, etc.)
  3. 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:

  1. The AuthenticateAsync method performs authentication logic
  2. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 144 5/20/2025
1.0.0 145 5/17/2025