Prowl.Aspect 1.0.0-preview.2

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

Prowl.Aspect

Github top languages GitHub license Discord

<span id="readme-top"></span>

Table of Contents

  1. About The Project
  2. Features
  3. Getting Started
  4. Core Features
  5. Project Structure
  6. Implementation Status
  7. Advanced Examples
  8. Troubleshooting
  9. Contributing
  10. Part of the Prowl Ecosystem
  11. 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 exception
  • ThrowException - 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:

  1. Check that the NuGet package is properly installed
  2. Verify ProwlAspectWeavingEnabled is not set to false
  3. 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:

  1. Documentation - More examples, tutorials, and use cases
  2. Performance - Benchmarks and optimizations
  3. Async Support - Add support for async/await patterns
  4. More Aspects - Implement additional real-world aspect patterns

Building from Source

  1. Clone the repository:

    git clone https://github.com/ProwlEngine/Prowl.Aspect.git
    cd Prowl.Aspect
    
  2. Build all projects:

    dotnet build
    
  3. Run tests:

    dotnet test Aspect.Tests
    

Creating a Local NuGet Package

  1. Build in Release mode:

    dotnet build Aspect.Weaver -c Release
    dotnet build Aspect.Weaver.Host -c Release
    dotnet build Aspect -c Release
    
  2. Create the package:

    dotnet pack Aspect/Aspect.csproj -c Release -o ./nupkg
    
  3. 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
    

<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:

Join our Discord server! 🎉

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Dependencies 📦

<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


Join our Discord server! 🎉

Discord

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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