Prowl.Aspect
1.0.0-preview.2
dotnet add package Prowl.Aspect --version 1.0.0-preview.2
NuGet\Install-Package Prowl.Aspect -Version 1.0.0-preview.2
<PackageReference Include="Prowl.Aspect" Version="1.0.0-preview.2" />
<PackageVersion Include="Prowl.Aspect" Version="1.0.0-preview.2" />
<PackageReference Include="Prowl.Aspect" />
paket add Prowl.Aspect --version 1.0.0-preview.2
#r "nuget: Prowl.Aspect, 1.0.0-preview.2"
#:package Prowl.Aspect@1.0.0-preview.2
#addin nuget:?package=Prowl.Aspect&version=1.0.0-preview.2&prerelease
#tool nuget:?package=Prowl.Aspect&version=1.0.0-preview.2&prerelease
Prowl.Aspect
<span id="readme-top"></span>
Table of Contents
- About The Project
- Features
- Getting Started
- Core Features
- Project Structure
- Implementation Status
- Advanced Examples
- Troubleshooting
- Contributing
- Part of the Prowl Ecosystem
- License
<span align="center">📝 About The Project 📝</span>
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.
🎯 Current Status
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
<span align="center">✨ Features ✨</span>
- 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
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">🚀 Getting Started 🚀</span>
Installation
Install via NuGet (once published):
dotnet add package Prowl.Aspect
Or download from NuGet.org
Create an Aspect
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}");
}
}
Apply the Aspect
public class MyService
{
[Logging]
public void DoWork()
{
Console.WriteLine("Working...");
}
}
Build Your Project
With the NuGet package installed, weaving happens automatically during build:
dotnet build
You'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.dll
<p align="right">(<a href="#readme-top">back to top</a>)</p>
Configuration
Disable Automatic Weaving
To temporarily disable weaving for a project:
<PropertyGroup>
<ProwlAspectWeavingEnabled>false</ProwlAspectWeavingEnabled>
</PropertyGroup>
Custom Weaver Path
If needed, specify a custom weaver location:
<PropertyGroup>
<ProwlAspectWeaverPath>C:\path\to\Aspect.Weaver.Host.exe</ProwlAspectWeaverPath>
</PropertyGroup>
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">🔧 Core Features 🔧</span>
Method Interception (OnMethodBoundaryAspect)
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)
}
}
Property Interception (LocationInterceptionAspect)
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
}
}
Flow Behavior Control
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
Argument & Return Value Modification
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();
}
}
}
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">📦 Project Structure 📦</span>
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
<span align="center">⚙️ Implementation Status ⚙️</span>
✅ Completed & Working (100% - All 86 Tests Passing)
- 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
📋 Planned
- Async method support
- real-world examples and tutorials
- Visual Studio integration
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">🎨 Advanced Examples 🎨</span>
Caching
[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;
}
Retry Logic
[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
}
INotifyPropertyChanged
[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; }
}
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">🔧 Troubleshooting 🔧</span>
Weaving Not Running
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
Assembly Already Woven Error
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 build
Performance Impact
Prowl.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.
<span align="center">🤝 Contributing 🤝</span>
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
Building from Source
Clone the repository:
git clone https://github.com/ProwlEngine/Prowl.Aspect.git cd Prowl.AspectBuild all projects:
dotnet buildRun tests:
dotnet test Aspect.Tests
Creating a Local NuGet Package
Build in Release mode:
dotnet build Aspect.Weaver -c Release dotnet build Aspect.Weaver.Host -c Release dotnet build Aspect -c ReleaseCreate the package:
dotnet pack Aspect/Aspect.csproj -c Release -o ./nupkgTest 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
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">🌟 Part of the Prowl Ecosystem 🌟</span>
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
Join our Discord server! 🎉
<p align="right">(<a href="#readme-top">back to top</a>)</p>
Dependencies 📦
- Mono.Cecil - IL weaving engine
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<span align="center">📄 License 📄</span>
Distributed under the MIT License. See LICENSE for more information.
<p align="right">(<a href="#readme-top">back to top</a>)</p>
🙏 Acknowledgments
- Inspired by PostSharp
- Built with Mono.Cecil
- Test framework: xUnit
Join our Discord server! 🎉
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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 is compatible. 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 is compatible. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
-
net10.0
- No dependencies.
-
net6.0
- No dependencies.
-
net7.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
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.0-preview.2 | 349 | 11/21/2025 |
| 1.0.0-preview.1 | 351 | 11/20/2025 |