Skip to content

ProwlEngine/Prowl.Aspect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

31 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Prowl.Aspect

Github top languages GitHub license Discord

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

πŸ“ About The Project πŸ“

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

✨ Features ✨

  • 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

(back to top)

πŸš€ Getting Started πŸš€

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

(back to top)

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>

(back to top)

πŸ”§ Core Features πŸ”§

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();
        }
    }
}

(back to top)

πŸ“¦ Project Structure πŸ“¦

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

βš™οΈ Implementation Status βš™οΈ

βœ… 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

(back to top)

🎨 Advanced Examples 🎨

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; }
}

(back to top)

πŸ”§ Troubleshooting πŸ”§

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.

🀝 Contributing 🀝

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

(back to top)

🌟 Part of the Prowl Ecosystem 🌟

Prowl.Aspect is part of the Prowl game engine ecosystem, which includes but not limited to:

(back to top)

Dependencies πŸ“¦

(back to top)

πŸ“„ License πŸ“„

Distributed under the MIT License. See LICENSE for more information.

(back to top)


πŸ™ Acknowledgments


Discord

About

A Tiny Aspect-Oriented Library for .NET

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages