- About The Project
- Features
- Getting Started
- Core Features
- Project Structure
- Implementation Status
- Advanced Examples
- Troubleshooting
- Contributing
- Part of the Prowl Ecosystem
- License
Prowl.Aspect is an open-source, MIT-licensed Aspect-Oriented Programming (AOP) framework for C#, inspired by PostSharp. It uses IL weaving with Mono.Cecil to inject aspect behavior into your assemblies, enabling powerful cross-cutting concerns like logging, caching, validation, and more.
Production Ready - All 86 tests passing (100%)
- β Method Interception - OnEntry/OnSuccess/OnException/OnExit lifecycle hooks
- β Property Interception - OnGetValue/OnSetValue with value modification
- β Flow Behavior Control - Skip methods, suppress exceptions, modify execution flow
- β Argument & Return Value Modification - Full control over method parameters and results
- β Multi-Targeting Support - .NET Standard 2.1, .NET 6, 7, 8, 9, and 10
- β Double-Weaving Protection - Assemblies are marked after weaving to prevent accidental re-weaving
- Compile-Time Weaving - Zero runtime overhead, aspects are woven into IL during build
- PostSharp-Compatible API - Familiar syntax for developers coming from PostSharp
- Multi-Targeting - Supports .NET Standard 2.1, .NET 6, 7, 8, 9, and 10
- Method Lifecycle Hooks - OnEntry, OnSuccess, OnException, OnExit
- Property Interception - Full control over getters and setters
- Flow Control - Skip method execution, suppress exceptions, modify return values
- Method Interception - Full control over method invocation with Proceed() semantics
- Comprehensive Testing - 86 tests covering all features and edge cases
- Open Source - MIT license, community-driven development
Install via NuGet (once published):
dotnet add package Prowl.AspectOr download from NuGet.org
using Aspect;
[AttributeUsage(AttributeTargets.Method)]
public class LoggingAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine($"Entering {args.Method.Name}");
}
public override void OnExit(MethodExecutionArgs args)
{
Console.WriteLine($"Exiting {args.Method.Name}");
}
}public class MyService
{
[Logging]
public void DoWork()
{
Console.WriteLine("Working...");
}
}With the NuGet package installed, weaving happens automatically during build:
dotnet buildYou'll see weaving messages in the build output:
Prowl.Aspect: Weaving aspects into obj\Debug\net8.0\MyApp.dll
Weaving method: MyNamespace.MyClass::DoWork() with 1 aspect(s)
Prowl.Aspect: Weaving completed successfully
Note: If not using the NuGet package, you can manually run the weaver:
Aspect.Weaver.Host.exe bin/Debug/net10.0/MyApp.dllTo temporarily disable weaving for a project:
<PropertyGroup>
<ProwlAspectWeavingEnabled>false</ProwlAspectWeavingEnabled>
</PropertyGroup>If needed, specify a custom weaver location:
<PropertyGroup>
<ProwlAspectWeaverPath>C:\path\to\Aspect.Weaver.Host.exe</ProwlAspectWeaverPath>
</PropertyGroup>Intercept method execution with lifecycle hooks:
public class MyAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
// Called before method executes
// Can modify arguments, skip method, or throw
}
public override void OnSuccess(MethodExecutionArgs args)
{
// Called after successful execution
// Can modify return value
}
public override void OnException(MethodExecutionArgs args)
{
// Called when exception occurs
// Can suppress, replace, or rethrow exception
}
public override void OnExit(MethodExecutionArgs args)
{
// Always called (like finally)
}
}Intercept property getters and setters:
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
public override void OnSetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue(); // Get old value
var oldValue = args.Value;
args.ProceedSetValue(); // Set new value
if (!Equals(oldValue, args.Value))
{
// Raise PropertyChanged event
RaisePropertyChanged(args.Instance, args.Property.Name);
}
}
public override void OnGetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue(); // Get the actual value
// Can modify args.Value before returning
}
}Control execution flow from aspects:
public class CacheAttribute : OnMethodBoundaryAspect
{
private static Dictionary<string, object> _cache = new();
public override void OnEntry(MethodExecutionArgs args)
{
var key = GenerateCacheKey(args);
if (_cache.TryGetValue(key, out var cachedValue))
{
args.ReturnValue = cachedValue;
args.FlowBehavior = FlowBehavior.Return; // Skip method execution
}
}
public override void OnSuccess(MethodExecutionArgs args)
{
var key = GenerateCacheKey(args);
_cache[key] = args.ReturnValue;
}
}FlowBehavior options:
Continue- Normal execution (default)Return- Skip method execution or suppress exceptionThrowException- Throw custom exception
public class ArgumentValidationAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
// Modify arguments
if (args.Arguments[0] is int value && value < 0)
{
args.Arguments[0] = 0; // Clamp to zero
}
}
}
public class TransformResultAttribute : OnMethodBoundaryAspect
{
public override void OnSuccess(MethodExecutionArgs args)
{
// Modify return value
if (args.ReturnValue is string str)
{
args.ReturnValue = str.ToUpper();
}
}
}Prowl.Aspect/
βββ Aspect/ # Core library with aspect attributes
β βββ OnMethodBoundaryAspect.cs # Base class for method interception
β βββ MethodInterceptionAspect.cs # Advanced method interception
β βββ LocationInterceptionAspect.cs # Base class for property interception
β βββ MethodExecutionArgs.cs # Context for method interception
β βββ LocationInterceptionArgs.cs # Context for property interception
β βββ FlowBehavior.cs # Enum for controlling execution flow
β βββ Arguments.cs # Wrapper for method arguments
β
βββ Aspect.Weaver/ # IL weaving engine using Mono.Cecil
β βββ ModuleWeaver.cs # Main orchestrator
β βββ WeaverBase.cs # Shared weaving logic
β βββ MethodBoundaryAspectWeaver.cs # Weaves method boundary aspects
β βββ MethodInterceptionAspectWeaver.cs # Weaves method interception
β βββ LocationInterceptionAspectWeaver.cs # Weaves property aspects
β
βββ Aspect.Weaver.Host/ # Console app to run the weaver
β βββ Program.cs # CLI entry point
β
βββ Aspect.Tests/ # 86 comprehensive tests
βββ MethodInterceptionTests.cs # Method lifecycle tests
βββ PropertyInterceptionTests.cs # Property interception tests
βββ FlowBehaviourTests.cs # Flow control tests
βββ PracticalAspectsTests.cs # Real-world examples
βββ AdvancedPracticalAspectsTests.cs # Advanced scenarios
βββ TestAspects.cs # Shared aspect implementations
- Full API design with 86 comprehensive tests
- Core aspect attribute classes (OnMethodBoundaryAspect, MethodInterceptionAspect, LocationInterceptionAspect)
- IL weaver infrastructure with Mono.Cecil
- Method boundary aspects (OnEntry/OnSuccess/OnException/OnExit)
- Method interception aspects with Proceed() semantics
- Property interception (OnGetValue/OnSetValue)
- Flow behavior control (Continue/Return/ThrowException)
- Argument and return value modification
- Exception handling and suppression
- Multi-targeting (.NET Standard 2.1, .NET 6-10)
- Console weaver host application
- Double-weaving protection
- Async method support
- real-world examples and tutorials
- Visual Studio integration
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : OnMethodBoundaryAspect
{
private static Dictionary<string, object> _cache = new();
public override void OnEntry(MethodExecutionArgs args)
{
var key = $"{args.Method.Name}:{string.Join(",", args.Arguments)}";
if (_cache.TryGetValue(key, out var cachedValue))
{
args.ReturnValue = cachedValue;
args.FlowBehavior = FlowBehavior.Return;
}
}
public override void OnSuccess(MethodExecutionArgs args)
{
var key = $"{args.Method.Name}:{string.Join(",", args.Arguments)}";
_cache[key] = args.ReturnValue;
}
}
// Usage
[Cache]
public int ExpensiveCalculation(int x, int y)
{
Thread.Sleep(1000);
return x + y;
}[AttributeUsage(AttributeTargets.Method)]
public class RetryAttribute : MethodInterceptionAspect
{
public int MaxRetries { get; set; } = 3;
public override void OnInvoke(MethodInterceptionArgs args)
{
Exception lastException = null;
for (int attempt = 0; attempt < MaxRetries; attempt++)
{
try
{
args.Proceed();
return;
}
catch (Exception ex)
{
lastException = ex;
}
}
throw lastException;
}
}
// Usage
[Retry(MaxRetries = 5)]
public void UnreliableOperation()
{
// May fail occasionally
}[AttributeUsage(AttributeTargets.Property)]
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
public override void OnSetValue(LocationInterceptionArgs args)
{
var newValue = args.Value;
args.ProceedGetValue();
var oldValue = args.Value;
if (!Equals(oldValue, newValue))
{
args.Value = newValue;
args.ProceedSetValue();
if (args.Instance is INotifyPropertyChanged inpc)
{
RaisePropertyChanged(inpc, args.Property.Name);
}
}
}
public override void OnGetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue();
}
private void RaisePropertyChanged(INotifyPropertyChanged instance, string propertyName)
{
var eventDelegate = instance.GetType()
.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic)
?.GetValue(instance) as PropertyChangedEventHandler;
eventDelegate?.Invoke(instance, new PropertyChangedEventArgs(propertyName));
}
}
// Usage
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChanged]
public string Name { get; set; }
}Symptoms: No "Prowl.Aspect: Weaving" messages in build output
Solutions:
- Check that the NuGet package is properly installed
- Verify
ProwlAspectWeavingEnabledis not set tofalse - Build with detailed verbosity:
dotnet build -v detailed
Symptoms: "Assembly has already been woven by Aspect" message
Explanation: This is normal! The weaver detected double-weaving protection and skipped re-weaving.
Solution: If you need a fresh weave:
dotnet clean
dotnet buildProwl.Aspect uses compile-time weaving - there is zero runtime overhead. All aspect code is injected directly into your IL during build. The performance is identical to hand-written code.
Contributions are welcome! Areas where you can help:
- Documentation - More examples, tutorials, and use cases
- Performance - Benchmarks and optimizations
- Async Support - Add support for async/await patterns
- More Aspects - Implement additional real-world aspect patterns
-
Clone the repository:
git clone https://github.com/ProwlEngine/Prowl.Aspect.git cd Prowl.Aspect -
Build all projects:
dotnet build
-
Run tests:
dotnet test Aspect.Tests
-
Build in Release mode:
dotnet build Aspect.Weaver -c Release dotnet build Aspect.Weaver.Host -c Release dotnet build Aspect -c Release
-
Create the package:
dotnet pack Aspect/Aspect.csproj -c Release -o ./nupkg
-
Test locally:
# Add local source dotnet nuget add source ./nupkg --name ProwlLocal # Create test project dotnet new console -n TestAspect cd TestAspect dotnet add package Prowl.Aspect --version 1.0.0-preview.1 --source ProwlLocal
Prowl.Aspect is part of the Prowl game engine ecosystem, which includes but not limited to:
- Prowl Engine - Open-source Unity-like game engine
- Prowl.Paper - Immediate-mode UI library
- Prowl.Quill - Vector Graphics library
- Prowl.Scribe - Font rendering library
- Mono.Cecil - IL weaving engine
Distributed under the MIT License. See LICENSE for more information.
- Inspired by PostSharp
- Built with Mono.Cecil
- Test framework: xUnit