Lightweight AOP (Aspect-Oriented Programming) library with PostSharp-compatible API. Supports method interception, boundary aspects, and property interception with compile-time IL weaving via Fody. Single package includes both library and Fody weaver.
$ dotnet add package DotNetAspectsA lightweight, high-performance AOP library for .NET
The free, open-source alternative to PostSharp
Features | Quick Start | Performance | Migration from PostSharp | Documentation
| PostSharp | DotNetAspects | |
|---|---|---|
| Cost | $499-$4,999/year per developer | Free forever |
| License | Proprietary | MIT (Open Source) |
| Weaving | Compile-time IL | Compile-time IL (Fody) |
| API Compatibility | - | PostSharp-compatible |
| Performance | Excellent | Excellent |
DotNetAspects provides the same powerful AOP capabilities as PostSharp with a compatible API, making migration straightforward while eliminating licensing costs.
Proceed() support97f295f398ec39b7netstandard2.0 and net8.0dotnet add package DotNetAspects
dotnet add package Fody
using DotNetAspects.Interception;
using DotNetAspects.Args;
public class LoggingAspect : MethodInterceptionAspect
{
public override void OnInvoke(MethodInterceptionArgs args)
{
Console.WriteLine($"Calling: {args.Method.Name}");
args.Proceed(); // Execute the original method
Console.WriteLine($"Result: {args.ReturnValue}");
}
}
public class OrderService
{
[LoggingAspect]
public Order ProcessOrder(int orderId, decimal amount)
{
// Your business logic here
return new Order { Id = orderId, Total = amount };
}
}
dotnet build
That's it! The aspect is woven at compile time - no runtime configuration needed.
DotNetAspects v1.4.0 is optimized for high-throughput enterprise scenarios:
| Scenario | Operations | Time | Throughput |
|---|---|---|---|
| Method Interception | 10,000 | 27ms | ~370,000 ops/sec |
| Concurrent Access (100 threads) | 50,000 | 38ms | ~1.3M ops/sec |
| Banking Transactions | 3,000 | <5ms | ~600,000 ops/sec |
GetRawArray() avoids unnecessary array allocationscd tests/DotNetAspects.LoadTests
dotnet run -c Release
<ItemGroup>
<PackageReference Include="DotNetAspects" Version="1.4.0" />
<PackageReference Include="Fody" Version="6.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
dotnet add package DotNetAspects --version 1.4.0
dotnet add package Fody
Note: No
FodyWeavers.xmlis required - the weaver is configured automatically.
Intercept any method call with full control:
public class CachingAspect : MethodInterceptionAspect
{
private static readonly Dictionary<string, object> Cache = new();
public override void OnInvoke(MethodInterceptionArgs args)
{
var key = $"{args.Method.Name}:{string.Join(",", args.Arguments)}";
if (Cache.TryGetValue(key, out var cached))
{
args.ReturnValue = cached;
return; // Skip original method
}
args.Proceed();
Cache[key] = args.ReturnValue;
}
}
Execute code at specific points in method execution:
public class TimingAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
args.Tag = Stopwatch.StartNew();
}
public override void OnSuccess(MethodExecutionArgs args)
{
Console.WriteLine($"{args.Method.Name} completed successfully");
}
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine($"{args.Method.Name} threw: {args.Exception.Message}");
}
public override void OnExit(MethodExecutionArgs args)
{
var sw = (Stopwatch)args.Tag;
Console.WriteLine($"{args.Method.Name} took {sw.ElapsedMilliseconds}ms");
}
}
Intercept property get/set operations:
public class NotifyChangedAspect : LocationInterceptionAspect
{
public override void OnGetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue();
Console.WriteLine($"Get {args.LocationName}: {args.Value}");
}
public override void OnSetValue(LocationInterceptionArgs args)
{
var oldValue = args.GetCurrentValue();
args.ProceedSetValue();
if (!Equals(oldValue, args.Value))
{
Console.WriteLine($"{args.LocationName} changed: {oldValue} -> {args.Value}");
}
}
}
public class RetryAspect : MethodInterceptionAspect
{
public int MaxRetries { get; set; } = 3;
public int DelayMs { get; set; } = 1000;
public override void OnInvoke(MethodInterceptionArgs args)
{
for (int i = 0; i <= MaxRetries; i++)
{
try
{
args.Proceed();
return;
}
catch when (i < MaxRetries)
{
Thread.Sleep(DelayMs);
}
}
}
}
// Usage
[RetryAspect(MaxRetries = 5, DelayMs = 500)]
public void UnreliableOperation() { }
DotNetAspects provides a PostSharp-compatible API for easy migration:
- using PostSharp.Aspects;
- using PostSharp.Serialization;
+ using DotNetAspects.Interception;
+ using DotNetAspects.Args;
- [PSerializable] // Not required in DotNetAspects
public class MyAspect : MethodInterceptionAspect
{
// No changes needed to the aspect logic!
}
| PostSharp | DotNetAspects |
|---|---|
PostSharp.Aspects.MethodInterceptionAspect | DotNetAspects.Interception.MethodInterceptionAspect |
PostSharp.Aspects.OnMethodBoundaryAspect | DotNetAspects.Interception.OnMethodBoundaryAspect |
PostSharp.Aspects.LocationInterceptionAspect | DotNetAspects.Interception.LocationInterceptionAspect |
PostSharp.Aspects.MethodInterceptionArgs | DotNetAspects.Args.MethodInterceptionArgs |
PostSharp.Aspects.MethodExecutionArgs | DotNetAspects.Args.MethodExecutionArgs |
[PSerializable] | Not required |
Detailed Guide: See MIGRATION_GUIDE.md for enterprise migration strategies.
Version History: See CHANGELOG.md for all releases.
| Property/Method | Description |
|---|---|
Instance | Object instance (null for static methods) |
Method | MethodBase of the intercepted method |
Arguments | Method arguments (IArguments) |
ReturnValue | Get/set the return value |
Proceed() | Execute original method with current arguments |
Invoke(args) | Execute original method with custom arguments |
| Property/Method | Description |
|---|---|
Instance | Object instance (null for static methods) |
Method | MethodBase of the executing method |
Arguments | Method arguments (IArguments) |
ReturnValue | Return value (in OnSuccess/OnExit) |
Exception | Exception thrown (in OnException) |
Tag | Pass state between aspect methods |
FlowBehavior | Control flow after aspect returns |
| Property/Method | Description |
|---|---|
Instance | Object instance (null for static) |
Location | PropertyInfo/FieldInfo |
LocationName | Name of the property/field |
Value | Value being get/set |
GetCurrentValue() | Get current value from location |
SetNewValue(value) | Set value to location |
ProceedGetValue() | Proceed with get operation |
ProceedSetValue() | Proceed with set operation |
src/
├── DotNetAspects/ # Core library
│ ├── Args/ # Argument classes
│ ├── Interception/ # Aspect base classes
│ └── Extensibility/ # Multicast attributes
└── DotNetAspects.Fody/ # IL weaver
tests/
├── DotNetAspects.Tests/ # Unit tests
└── DotNetAspects.LoadTests/ # Performance benchmarks
Contributions are welcome! Here's how to get started:
git clone https://github.com/your-username/DotNetAspects.gitgit checkout -b feature/amazing-featuredotnet testgit commit -m 'Add amazing feature'git push origin feature/amazing-featuregit clone https://github.com/RlyehDoom/DotNetAspects.git
cd DotNetAspects
dotnet build
dotnet test
cd tests/DotNetAspects.LoadTests
dotnet run -c Release
This project is licensed under the MIT License - see the LICENSE file for details.
Made with love for the .NET community
If you find this project useful, please consider giving it a star!