|
1 | | -[ASPeKT] |
2 | | -=== |
| 1 | +# Aspekt - Aspect-Oriented Programming for .NET |
3 | 2 |
|
4 | 3 | [](https://ci.appveyor.com/project/mvpete/aspekt) |
| 4 | +[](https://dotnet.microsoft.com/) |
| 5 | +[](LICENSE) |
5 | 6 |
|
6 | | -A lightweight (Aspect Oriented Programming) AOP foundation |
| 7 | +Aspekt is a lightweight, powerful Aspect-Oriented Programming (AOP) foundation library for .NET that allows you to implement cross-cutting concerns using attributes. It supports modern .NET versions including .NET 6.0, 8.0, and 9.0, with async/await patterns and comprehensive Design by Contract capabilities. |
7 | 8 |
|
8 | | -### Overview |
9 | | -Aspekt is a small AOP foundation library written in C#. Aspect Oriented Programming (AOP) allows to ease the pain associated with cross-cutting concerns. A cross-cutting concern is a pattern that gets across a program that cannot easily be factored into its own module. This raises the so called signal-to-noise ratio. One common cross cutting concern is logging, when logging entry/exit/exception of a function, the code becomes cluttered with log code and the intent can become lost. Aspekt addresses these concerns, by using attributes as annotation of functions. Like PostSharp aspect utilizes post processing of the .NET binaries, inserting the aspects post build. For ASPeKT this is done using Mono.Cecil. |
| 9 | +## 🚀 Key Features |
10 | 10 |
|
11 | | -### Usage |
| 11 | +- **Attribute-Based AOP**: Apply aspects declaratively using C# attributes |
| 12 | +- **Async Support**: Full support for async/await patterns with `ValueTask` methods |
| 13 | +- **Design by Contract**: Comprehensive contract system with preconditions, postconditions, and invariants |
| 14 | +- **Return Value Interception**: Modify return values using `IAspectExitHandler<T>` |
| 15 | +- **Modern .NET Support**: Compatible with .NET 6.0, 8.0, 9.0, and .NET Standard 2.1 |
| 16 | +- **Post-Compilation Weaving**: IL manipulation using Mono.Cecil for zero-overhead aspect application |
| 17 | +- **Built-in Logging**: Ready-to-use logging aspects with customizable formatters |
| 18 | +- **Thread-Safe**: Designed for multi-threaded applications |
| 19 | + |
| 20 | +## 📦 Installation |
| 21 | + |
| 22 | +```bash |
| 23 | +# Core AOP functionality |
| 24 | +Install-Package Aspekt |
| 25 | + |
| 26 | +# Design by Contract support |
| 27 | +Install-Package Aspekt.Contracts |
12 | 28 |
|
13 | | -The foundation of Aspekt is the base Aspect. In order to utilize Aspects, just derive from Aspekt.Aspect and implement the OnEntry, OnExit, OnException methods. Aspekt will only place the calls implemented on the class i.e. OnEntry, OnExit, OnException |
| 29 | +# Logging aspects |
| 30 | +Install-Package Aspekt.Logging |
| 31 | +``` |
| 32 | + |
| 33 | +## 🏃♂️ Quick Start |
14 | 34 |
|
| 35 | +### Basic Logging Aspect |
15 | 36 |
|
16 | 37 | ```csharp |
17 | | -class SampleAspect : Aspekt.Aspect |
| 38 | +using Aspekt; |
| 39 | + |
| 40 | +public class LoggingAspect : Aspect |
18 | 41 | { |
19 | | - public SampleAspect(String val) |
20 | | - { |
21 | | - } |
22 | | - |
23 | | - public void override OnEntry(MethodArgs ma) |
24 | | - { |
25 | | - // called before any existing code is ran |
26 | | - } |
27 | | - |
28 | | - public void override OnExit(MethodArgs ma) |
29 | | - { |
30 | | - // called before ANY return statement |
31 | | - } |
32 | | - |
33 | | - public void override OnException(MethodArgs ma, Exception e) |
34 | | - { |
35 | | - // called if existing codes excepts |
36 | | - } |
| 42 | + public override void OnEntry(MethodArguments args) |
| 43 | + { |
| 44 | + Console.WriteLine($"Entering: {args.FullName}"); |
| 45 | + } |
| 46 | + |
| 47 | + public override void OnExit(MethodArguments args) |
| 48 | + { |
| 49 | + Console.WriteLine($"Exiting: {args.FullName}"); |
| 50 | + } |
| 51 | + |
| 52 | + public override void OnException(MethodArguments args, Exception ex) |
| 53 | + { |
| 54 | + Console.WriteLine($"Exception in {args.FullName}: {ex.Message}"); |
| 55 | + } |
37 | 56 | } |
38 | 57 | ``` |
39 | 58 |
|
40 | | -When you use the Aspect in your client code, such as |
| 59 | +### Usage |
41 | 60 |
|
42 | 61 | ```csharp |
43 | | -class Foo |
| 62 | +public class Calculator |
44 | 63 | { |
45 | | - [SampleAspect("Some Value")] |
46 | | - public void Bar(String s, int i) |
| 64 | + [Logging] |
| 65 | + public int Add(int x, int y) |
47 | 66 | { |
48 | | - // Do something here. |
| 67 | + return x + y; |
| 68 | + } |
| 69 | + |
| 70 | + [Logging] |
| 71 | + public async Task<string> GetDataAsync() |
| 72 | + { |
| 73 | + await Task.Delay(100); |
| 74 | + return "Hello, World!"; |
49 | 75 | } |
50 | 76 | } |
51 | 77 | ``` |
52 | 78 |
|
53 | | -Aspekt will re-write the code, to something along the lines of the following. |
| 79 | +### Advanced: Return Value Modification |
54 | 80 |
|
55 | 81 | ```csharp |
56 | | -class Foo |
| 82 | +public class ResultModifierAspect : Aspect, IAspectExitHandler<string> |
57 | 83 | { |
58 | | - [SampleAspect("Some Value")] |
59 | | - public void Bar(String s, int i) |
| 84 | + public string OnExit(MethodArguments args, string result) |
60 | 85 | { |
61 | | - MethodArgs ma = new MethodArgs("Bar", "Assembly.Foo.Bar(String s, int i)", new Arguments(new object[] { s, i }), this); |
62 | | - SampleAspect sa = new SampleAspect("Some Value"); |
63 | | - sa.OnEntry(ma); |
64 | | - try |
65 | | - { |
66 | | - // Do Something Here |
67 | | - sa.OnExit(ma); |
68 | | - } |
69 | | - catch(Exception e) |
70 | | - { |
71 | | - sa.OnException(ma,e); |
72 | | - throw; |
73 | | - } |
| 86 | + return $"Modified: {result}"; |
74 | 87 | } |
75 | 88 | } |
76 | | - ``` |
77 | | - As mentioned earlier, Aspekt will only write functions where they've been overridden. This means, only the methods that you want, are added. As well, Aspekt tries not alter or modify existing code, so if the IL contains multiple returns, Aspekt calls OnExit before each return. |
78 | 89 |
|
79 | | -If you're using NuGet to get ASPeKT, your project will have the appropriate post build steps. You can ignore anything below. |
| 90 | +public class Service |
| 91 | +{ |
| 92 | + [ResultModifier] |
| 93 | + public string GetMessage() => "Original"; |
| 94 | + // Returns: "Modified: Original" |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +### Design by Contract |
| 99 | + |
| 100 | +```csharp |
| 101 | +using Aspekt.Contracts; |
80 | 102 |
|
81 | | -Since Aspekt works post compile, in order to use it you must run the Bootstrap application against your assembly. |
| 103 | +public class BankAccount |
| 104 | +{ |
| 105 | + private decimal _balance; |
82 | 106 |
|
83 | | - > Aspekt.Bootstrap.Host [PathToAssembly] |
| 107 | + [Invariant(nameof(_balance), Contract.Comparison.GreaterThanEqualTo, 0)] |
| 108 | + public decimal Balance => _balance; |
| 109 | + |
| 110 | + [Require(nameof(amount), Contract.Comparison.GreaterThan, 0)] |
| 111 | + [Ensure(Contract.Comparison.GreaterThanEqualTo, 0)] |
| 112 | + public decimal Deposit(decimal amount) |
| 113 | + { |
| 114 | + _balance += amount; |
| 115 | + return _balance; |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +## 📚 Documentation |
| 121 | + |
| 122 | +- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Complete guide from installation to advanced features |
| 123 | +- **[Contracts Documentation](docs/CONTRACTS.md)** - Design by Contract with preconditions, postconditions, and invariants |
| 124 | +- **[API Reference](docs/API.md)** - Detailed API documentation for all components |
| 125 | +- **[Examples](docs/EXAMPLES.md)** - Real-world usage examples and patterns |
| 126 | +- **[Troubleshooting Guide](docs/TROUBLESHOOTING.md)** - Common issues and solutions |
| 127 | + |
| 128 | +## 🔧 How It Works |
| 129 | + |
| 130 | +Aspekt uses post-compilation IL weaving via Mono.Cecil. When you apply an aspect attribute to a method: |
| 131 | + |
| 132 | +1. **Build Time**: Your code compiles normally |
| 133 | +2. **Post-Build**: Aspekt.Bootstrap.Host processes your assembly |
| 134 | +3. **IL Weaving**: Aspect calls are injected into your methods |
| 135 | +4. **Runtime**: Aspects execute seamlessly with your code |
| 136 | + |
| 137 | +```csharp |
| 138 | +// Your code: |
| 139 | +[LoggingAspect] |
| 140 | +public void DoWork() { /* your logic */ } |
| 141 | + |
| 142 | +// Becomes (conceptually): |
| 143 | +public void DoWork() |
| 144 | +{ |
| 145 | + var aspect = new LoggingAspect(); |
| 146 | + var args = new MethodArguments(/* method info */); |
| 147 | + |
| 148 | + aspect.OnEntry(args); |
| 149 | + try |
| 150 | + { |
| 151 | + /* your original logic */ |
| 152 | + aspect.OnExit(args); |
| 153 | + } |
| 154 | + catch (Exception ex) |
| 155 | + { |
| 156 | + aspect.OnException(args, ex); |
| 157 | + throw; |
| 158 | + } |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +## 🏗️ Project Structure |
| 163 | + |
| 164 | +- **Aspekt**: Core AOP functionality and base `Aspect` class |
| 165 | +- **Aspekt.Contracts**: Design by Contract implementation |
| 166 | +- **Aspekt.Logging**: Built-in logging aspects with multiple formatters |
| 167 | +- **Aspekt.Bootstrap**: IL weaving engine using Mono.Cecil |
| 168 | +- **Aspekt.Test**: Comprehensive test suite with 100+ tests |
| 169 | + |
| 170 | +## 🛠️ Build Requirements |
| 171 | + |
| 172 | +- .NET SDK 6.0 or later |
| 173 | +- Visual Studio 2022 or VS Code |
| 174 | +- MSBuild 17.0+ |
| 175 | + |
| 176 | +```bash |
| 177 | +# Clone and build |
| 178 | +git clone https://github.com/your-org/aspekt.git |
| 179 | +cd aspekt |
| 180 | +dotnet build |
| 181 | +dotnet test |
| 182 | +``` |
| 183 | + |
| 184 | +## ⚡ Performance |
| 185 | + |
| 186 | +Aspekt is designed for minimal runtime overhead: |
| 187 | +- **Zero reflection** at runtime |
| 188 | +- **Compile-time weaving** means no performance impact from AOP infrastructure |
| 189 | +- **Selective application** - only methods with aspects are modified |
| 190 | +- **Async-aware** - proper support for async/await patterns |
| 191 | + |
| 192 | +## 🤝 Contributing |
| 193 | + |
| 194 | +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. |
| 195 | + |
| 196 | +1. Fork the repository |
| 197 | +2. Create a feature branch |
| 198 | +3. Add tests for your changes |
| 199 | +4. Ensure all tests pass |
| 200 | +5. Submit a pull request |
| 201 | + |
| 202 | +## 📄 License |
| 203 | + |
| 204 | +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
| 205 | + |
| 206 | +## 🙏 Acknowledgments |
| 207 | + |
| 208 | +- Built with [Mono.Cecil](https://github.com/jbevain/cecil) for IL manipulation |
| 209 | +- Inspired by PostSharp and other AOP frameworks |
| 210 | +- Thanks to all contributors and users |
| 211 | + |
| 212 | +--- |
84 | 213 |
|
85 | | -This will process the assembly and add in the aspects to their respective members. |
| 214 | +**Get started today**: Check out the [Getting Started Guide](docs/GETTING_STARTED.md) to begin using Aspekt in your projects! |
86 | 215 |
|
87 | 216 |
|
0 commit comments