├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── application.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── AspectInjector-Key.snk ├── AspectInjector.sln ├── Directory.Build.props ├── LICENSE ├── README.md ├── aspects ├── Cache │ ├── AbstractAttributes.cs │ ├── Aspects.Cache.csproj │ ├── CacheAspect.cs │ ├── MemoryCacheAttribute.cs │ └── readme.md ├── Directory.Build.targets ├── Freezable │ ├── Aspects.Freezable.csproj │ ├── Freezable.cs │ └── readme.md ├── Lazy │ ├── Aspects.Lazy.csproj │ ├── LazyAspect.cs │ ├── LazyAttribute.cs │ ├── readme-cn.md │ └── readme.md ├── Logging │ ├── Aspects.Logging.csproj │ ├── LogAttribute.cs │ └── LoggingAspect.cs ├── Notify │ ├── Aspects.Notify.csproj │ ├── NotifyAspect.cs │ └── readme.md ├── Universal │ ├── Aspects.Universal.csproj │ ├── Aspects │ │ ├── BaseUniversalWrapperAspect.cs │ │ ├── MethodWrapperAspect.cs │ │ ├── PrivateMethodWrapperAspect.cs │ │ ├── PublicMethodWrapperAspect.cs │ │ └── PublicWrapperAspect.cs │ ├── Attributes │ │ ├── BaseMethodPointsAspectAttribute.cs │ │ ├── BaseUniversalWrapperAttribute.cs │ │ ├── MethodAspectAttribute.cs │ │ ├── PrivateMethodAspectAttribute.cs │ │ ├── PublicAspectAttribute.cs │ │ └── PublicMethodAspectAttribute.cs │ └── Events │ │ └── AspectEventArgs.cs └── readme.md ├── docs ├── advice.md ├── advicearguments.md ├── aspect.md ├── errors │ ├── AIAM001.md │ ├── AI_A000.md │ ├── AI_A001.md │ ├── AI_A002.md │ ├── AI_A003.md │ └── readme.md ├── injection.md ├── mixin.md ├── readme.md └── terminology.md ├── global.json ├── package.png ├── src ├── AspectInjector.Analyzer.Vsix │ ├── AspectInjector.Analyzer.Vsix.csproj │ └── source.extension.vsixmanifest ├── AspectInjector.Analyzer │ ├── Analyzers │ │ ├── AdviceAttributeAnalyzer.cs │ │ ├── ArgumentAttributeAnalyzer.cs │ │ ├── AspectAttributeAnalyzer.cs │ │ ├── InjectionAttributeAnalyzer.cs │ │ └── MixinAttributeAnalyzer.cs │ ├── AspectInjector.Analyzer.csproj │ ├── CodeFixes │ │ ├── AspectCodeFixProvider.cs │ │ └── MixinCodeFixProvider.cs │ ├── Extensions.cs │ ├── Refactorings │ │ ├── AdviceAttributeCodeRefactoringProvider.cs │ │ ├── AdviceCodeRefactoringProvider.cs │ │ ├── AspectAttributeCodeRefactoringProvider.cs │ │ ├── AspectCodeRefactoringProvider.cs │ │ ├── PossibleMixinCodeRefactoringProvider.cs │ │ ├── SampleAdvices.cs │ │ └── SampleParameters.cs │ ├── RoslynExtensions.cs │ ├── SyntaxBasicBlocks.cs │ └── WellKnown.cs ├── AspectInjector.Broker │ ├── Advice.cs │ ├── Argument.cs │ ├── Aspect.cs │ ├── AspectInjector.Broker.csproj │ ├── Injection.cs │ ├── Kind.cs │ ├── Mixin.cs │ ├── PropagateTo.cs │ ├── Scope.cs │ ├── SkipInjection.cs │ ├── Source.cs │ └── Target.cs ├── AspectInjector.Core.Advice │ ├── AdviceReader.cs │ ├── AspectInjector.Core.Advice.csproj │ ├── Constants.cs │ ├── Effects │ │ ├── AdviceArgument.cs │ │ ├── AdviceEffectBase.cs │ │ ├── AfterAdviceEffect.cs │ │ ├── AroundAdviceEffect.cs │ │ └── BeforeAdviceEffect.cs │ └── Weavers │ │ ├── AdviceAroundWeaver.cs │ │ ├── AdviceInlineWeaver.cs │ │ ├── AdviceStateMachineWeaver.cs │ │ └── Processes │ │ ├── AdviceAfterProcess.cs │ │ ├── AdviceAroundProcess.cs │ │ ├── AdviceBeforeProcess.cs │ │ ├── AdviceWeaveProcessBase.cs │ │ ├── AfterAsyncWeaveProcess.cs │ │ ├── AfterIteratorWeaveProcess.cs │ │ └── AfterStateMachineWeaveProcessBase.cs ├── AspectInjector.Core.Mixin │ ├── AspectInjector.Core.Mixin.csproj │ ├── MixinEffect.cs │ ├── MixinReader.cs │ ├── MixinWeaveProcess.cs │ └── MixinWeaver.cs ├── AspectInjector.Core │ ├── AspectInjector.Core.csproj │ ├── Constants.cs │ ├── Contracts │ │ ├── IAspectReader.cs │ │ ├── IAspectWeaver.cs │ │ ├── IEffectReader.cs │ │ ├── IEffectWeaver.cs │ │ └── IInjectionReader.cs │ ├── Extensions │ │ ├── CustomAttributeExtensions.cs │ │ └── FluentExtensions.cs │ ├── Models │ │ ├── AspectDefinition.cs │ │ ├── Assets.cs │ │ ├── Effect.cs │ │ └── InjectionDefinition.cs │ ├── Processor.cs │ ├── Services │ │ ├── AspectReader.cs │ │ ├── AspectWeaver.cs │ │ └── InjectionReader.cs │ └── WellKnownTypes.cs ├── AspectInjector.Rules │ ├── AspectInjector.Rules.csproj │ ├── AspectRules.cs │ ├── EffectRules.cs │ ├── GeneralRules.cs │ └── InjectionRules.cs ├── AspectInjector │ ├── AspectInjector.csproj │ ├── AspectInjectorTask.cs │ ├── Compiler.cs │ ├── MsBuildLogger.cs │ ├── build │ │ └── AspectInjector.targets │ └── tools │ │ ├── install.ps1 │ │ └── uninstall.ps1 ├── FluentIL.Common │ ├── FluentIL.Common.csproj │ └── Rule.cs └── FluentIL │ ├── Cuts │ ├── Arrays.cs │ ├── Statements.cs │ ├── TypeMembers.cs │ └── Values.cs │ ├── Extensions │ ├── CecilReferenceExtensions.cs │ ├── GenericsExtensions.cs │ ├── MemberDefinitionExtensions.cs │ ├── PointCutExtensions.cs │ └── TypeSystemExtension.cs │ ├── FluentIL.csproj │ ├── Logging │ ├── ILogger.cs │ ├── Logger.cs │ └── LoggerExtensions.cs │ ├── MethodEditor.cs │ ├── PatcherBase.cs │ ├── PointCut.cs │ ├── Resolvers │ ├── CachedAssemblyResolver.cs │ └── KnownReferencesAssemblyResolver.cs │ └── StandardTypes.cs └── tests ├── AspectInjector.Analyzer.Tests ├── Analyzers │ ├── AdviceAnalyzerTests.cs │ ├── ArgumentAnalyzerTests.cs │ ├── AspectAnalyzerTests.cs │ ├── CorrectDefinitionsTests.cs │ └── MixinAnalyzerTests.cs ├── AspectAttributeTests.cs ├── AspectInjector.Analyzer.Tests.csproj ├── Helpers │ ├── CodeFixVerifier.Helper.cs │ ├── DiagnosticResult.cs │ └── DiagnosticVerifier.Helper.cs ├── MixinAttributeTests.cs └── Verifiers │ ├── CodeFixVerifier.cs │ └── DiagnosticVerifier.cs ├── AspectInjector.Tests.Generics ├── AspectInjector.Tests.Generics.csproj ├── Class1.cs └── Class2.cs ├── AspectInjector.Tests.Integrity ├── AspectInjector.Tests.Integrity.csproj ├── PDBTest.cs └── PETest.cs ├── AspectInjector.Tests.Runtime ├── Advices │ ├── AccessModifiersTests.cs │ ├── AfterTests.cs │ ├── ArgumentsTests.cs │ ├── AroundTests.cs │ ├── AsyncTests.cs │ ├── BeforeTests.cs │ ├── CompilerGeneratedTargetsTests.cs │ ├── DebugTests.cs │ ├── FilterTests.cs │ ├── GenericTests.cs │ ├── IteratorTests.cs │ ├── NestedClassesTests.cs │ ├── OrderTests.cs │ └── StaticTests.cs ├── After │ ├── TargetsGlobalTests.cs │ └── TargetsIstanceTests.cs ├── Around │ ├── TargetsGlobalTests.cs │ └── TargetsIstanceTests.cs ├── AspectInjector.Tests.Runtime.csproj ├── Before │ ├── TargetsGlobalTests.cs │ └── TargetsIstanceTests.cs ├── Config.cs ├── Control.cs ├── Events.cs ├── General │ ├── AspectFactoryTests.cs │ ├── AspectScopeTests.cs │ ├── CustomAttributesTests.cs │ ├── PropagationControlTests.cs │ ├── ReferencesTests.cs │ └── UnmanagedTests.cs ├── InheritedInjections │ └── InheritedInjectionsTests.cs ├── Injections │ ├── DirectInjectionsTests.cs │ ├── InjectionOrderTests.cs │ ├── InterfaceTriggers.cs │ ├── SkipInjectionAttributeTests.cs │ └── SkipInjectionsTests.cs ├── Interfaces │ ├── GeneralTests.cs │ ├── GenericInterfacesTests.cs │ └── InheritanceTests.cs ├── Issues │ ├── 0097.cs │ ├── 0098.cs │ ├── 0114.cs │ ├── 0123.cs │ ├── 0140.cs │ └── 0142.cs ├── Mixins │ └── TestClass.cs ├── TestClass.cs ├── TestRunner.cs ├── Utils │ └── Checker.cs └── test.key.snk ├── AspectInjector.Tests.RuntimeAssets ├── AspectInjector.Tests.RuntimeAssets.csproj ├── CrossAssemblyHelpers │ ├── BaseAttribute.cs │ ├── BaseGenericClass.cs │ ├── SomeType.cs │ ├── TestAspect.cs │ └── TestBaseClass.cs ├── GlobalAspect.cs ├── InstanceAspect.cs ├── TestAspectBase.cs ├── TestAssets.cs ├── TestLog.cs └── key.snk ├── AspectInjector.Tests.VBRuntime ├── AspectInjector.Tests.VBRuntime.vbproj └── Issue_0182.vb ├── Aspects.Tests ├── Aspects.Tests.csproj ├── CacheTests.cs ├── FreezableTests.cs ├── LazyTests.cs └── NotifyTests.cs └── Directory.Build.targets /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.csproj] 7 | indent_size = 2 8 | 9 | [*.json] 10 | indent_size = 2 11 | 12 | [*.cs] 13 | indent_size = 4 14 | 15 | [*] 16 | end_of_line = lf 17 | charset = utf-8 18 | trim_trailing_whitespace = true 19 | insert_final_newline = true 20 | indent_style = space 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | # no eol conversions! 5 | * -text 6 | 7 | ############################################################################### 8 | # Set default behavior for command prompt diff. 9 | # 10 | # This is need for earlier builds of msysgit that does not have it on by 11 | # default for csharp files. 12 | # Note: This is only used by command line 13 | ############################################################################### 14 | #*.cs diff=csharp 15 | 16 | ############################################################################### 17 | # Set the merge driver for project and solution files 18 | # 19 | # Merging from the command prompt will add diff markers to the files if there 20 | # are conflicts (Merging from VS is not affected by the settings below, in VS 21 | # the diff markers are never inserted). Diff markers may cause the following 22 | # file extensions to fail to load in VS. An alternative would be to treat 23 | # these files as binary and thus will always conflict and require user 24 | # intervention with every merge. To do so, just uncomment the entries below 25 | ############################################################################### 26 | #*.sln merge=binary 27 | #*.csproj merge=binary 28 | #*.vbproj merge=binary 29 | #*.vcxproj merge=binary 30 | #*.vcproj merge=binary 31 | #*.dbproj merge=binary 32 | #*.fsproj merge=binary 33 | #*.lsproj merge=binary 34 | #*.wixproj merge=binary 35 | #*.modelproj merge=binary 36 | #*.sqlproj merge=binary 37 | #*.wwaproj merge=binary 38 | 39 | ############################################################################### 40 | # behavior for image files 41 | # 42 | # image files are treated as binary by default. 43 | ############################################################################### 44 | #*.jpg binary 45 | #*.png binary 46 | #*.gif binary 47 | 48 | ############################################################################### 49 | # diff behavior for common document formats 50 | # 51 | # Convert binary document formats to text before diffing them. This feature 52 | # is only available from the command line. Turn it on by uncommenting the 53 | # entries below. 54 | ############################################################################### 55 | #*.doc diff=astextplain 56 | #*.DOC diff=astextplain 57 | #*.docx diff=astextplain 58 | #*.DOCX diff=astextplain 59 | #*.dot diff=astextplain 60 | #*.DOT diff=astextplain 61 | #*.pdf diff=astextplain 62 | #*.PDF diff=astextplain 63 | #*.rtf diff=astextplain 64 | #*.RTF diff=astextplain 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Environment (please complete the following information):** 11 | - OS: [e.g. windows, linux, macos] 12 | - Framework: [e.g. net461, netcoreapp3.1, net6.0] 13 | - Type of application: [e.g. console, webapp, winforms, wpf, xamarin, mobile etc] 14 | - Version of AspectInjector: [e.g. 2.7.1] 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. An what is expected behavior. 18 | 19 | **To Reproduce** 20 | A steps to reproduce the bug. Or a description when it happens (in compile-time, in runtime). 21 | 22 | **Additional context** 23 | Add any other context about the problem here. Please add stack trace here. Please put it under spoiler if possible. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature: ' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or code snippets about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/application.yml: -------------------------------------------------------------------------------- 1 | name: Application 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - '**' 8 | pull_request: 9 | 10 | env: 11 | app_release: false 12 | app_conf: Release 13 | nuget_url: https://api.nuget.org/v3/index.json 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - uses: actions/setup-dotnet@v1 23 | with: 24 | dotnet-version: 6.0.x 25 | - uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: 8.0.x 28 | - name: Get version 29 | run: echo "app_version=`git describe --tags`" >> $GITHUB_ENV 30 | - name: Get release status 31 | run: echo "app_release=`(git describe --tags --exact-match>/dev/null 2>&1 && echo true) || echo false`" >> $GITHUB_ENV 32 | - name: Status 33 | run: echo "Release=$app_release, version=$app_version" 34 | - name: Compile 35 | run: dotnet build -c $app_conf -p:Version=$app_version -p:InformationalVersion="$app_version:$GITHUB_SHA" 36 | - name: Test net6 37 | run: dotnet test -c $app_conf --no-build -f net6.0 38 | - name: Test net8 39 | run: dotnet test -c $app_conf --no-build -f net8.0 40 | - name: Pack 41 | run: dotnet pack -c $app_conf -o `pwd`/artifacts -p:Version=$app_version -p:InformationalVersion="$app_version:$GITHUB_SHA" -p:CommitSHA=$GITHUB_SHA 42 | - uses: actions/upload-artifact@v3.2.1 43 | with: 44 | name: artifacts 45 | path: ./artifacts 46 | - name: Publish 47 | if: env.app_release == 'true' 48 | env: 49 | NUGETKEY: ${{ secrets.NUGET_KEY }} 50 | working-directory: artifacts 51 | run: dotnet nuget push ./*.nupkg -s $nuget_url -k $NUGETKEY 52 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Attach", 9 | "type": "coreclr", 10 | "request": "attach", 11 | "processId": "${command:pickProcess}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/bin": true, 4 | "**/obj": true 5 | }, 6 | "search.exclude": { 7 | "**/obj": true 8 | } 9 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build_injector", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/AspectInjector/AspectInjector.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "build_analyzer", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "build", 22 | "${workspaceFolder}/analyzers/AspectInjector.Analyzer/AspectInjector.Analyzer.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "build_runtime", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "build", 34 | "--no-incremental", 35 | "-c", 36 | "DebugTests", 37 | "-f", 38 | "net6.0", 39 | "${workspaceFolder}/tests/AspectInjector.Tests.Runtime/AspectInjector.Tests.Runtime.csproj", 40 | "/property:GenerateFullPaths=true", 41 | "/p:AspectInjector_Debug=true", 42 | "/p:AspectInjector_Verbose=true", 43 | "/consoleloggerparameters:NoSummary" 44 | ], 45 | "problemMatcher": "$msCompile", 46 | "dependsOn": ["build_injector"] 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /AspectInjector-Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pamidur/aspect-injector/2a4a154857f4389a96ff4a0421e97ff91e2e060d/AspectInjector-Key.snk -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Aspect Injector 4 | Aspect Injector Contributors 5 | Aspect Injector Contributors 6 | 12.0 7 | package.png 8 | https://github.com/pamidur/aspect-injector 9 | https://github.com/pamidur/aspect-injector 10 | https://raw.githubusercontent.com/pamidur/aspect-injector/master/package.png 11 | git 12 | $(CommitSHA) 13 | Apache-2.0 14 | 0.0.0 15 | false 16 | 17 | 18 | True 19 | $(MSBuildThisFileDirectory)AspectInjector-Key.snk 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /aspects/Cache/Aspects.Cache.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | aspect cache memorycache method methodcache 5 | 6 | This package provides cache aspect for your methods. Put [MemoryCache(seconds)] attribure on your methods. And enjoy hassle-free memory cache for your methods. 7 | Or implement your own cache mechanics by inheriting CacheAttribute class. 8 | Powered by AspectInjector. 9 | 10 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Cache 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /aspects/Cache/CacheAspect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Aspects.Cache 8 | { 9 | [Aspect(Scope.Global)] 10 | public class CacheAspect 11 | { 12 | private static readonly object NullMarker = new { __is_null = "$_is_null" }; 13 | 14 | [Advice(Kind.Around)] 15 | public object Handle( 16 | [Argument(Source.Target)] Func target, 17 | [Argument(Source.Arguments)] object[] args, 18 | [Argument(Source.Instance)] object instance, 19 | [Argument(Source.ReturnType)] Type retType, 20 | [Argument(Source.Triggers)] Attribute[] triggers 21 | ) 22 | { 23 | retType = retType == typeof(void) ? typeof(object) : retType; 24 | 25 | object result = null; 26 | var resultFound = false; 27 | 28 | var cacheTriggers = triggers.OfType().Distinct().OrderBy(c => c.Priority).ToList(); 29 | var key = GetKey(target.Method, args); 30 | 31 | 32 | foreach (var cacheTrigger in cacheTriggers) 33 | { 34 | var ci = cacheTrigger.Get(key, retType, instance); 35 | if (ci != null) 36 | { 37 | result = ci; 38 | if (result == NullMarker) 39 | result = null; 40 | 41 | resultFound = true; 42 | break; 43 | } 44 | } 45 | 46 | if (!resultFound) 47 | { 48 | result = target(args) ?? NullMarker; 49 | 50 | foreach (var cacheTrigger in cacheTriggers) 51 | { 52 | cacheTrigger.Set(key, result, retType, instance); 53 | } 54 | } 55 | if (result == NullMarker) return null; 56 | return result; 57 | } 58 | 59 | protected string GetKey(MethodInfo method, IEnumerable args) => 60 | $"{method.GetHashCode()}_{string.Join("_", args.Select(a => a?.GetHashCode() ?? NullMarker.GetHashCode()))}"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /aspects/Cache/MemoryCacheAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | 5 | namespace Aspects.Cache 6 | { 7 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 8 | public class MemoryCacheAttribute : MemoryCacheBaseAttribute 9 | { 10 | private static readonly MemoryCache _cache = new MemoryCache(new OptionsWrapper(new MemoryCacheOptions())); 11 | 12 | private readonly uint _seconds; 13 | 14 | public MemoryCacheAttribute(uint seconds, bool perInstanceCache = false) 15 | { 16 | _seconds = seconds; 17 | PerInstanceCache = perInstanceCache; 18 | } 19 | 20 | public override IMemoryCache Cache => _cache; 21 | 22 | public override MemoryCacheEntryOptions Policy => new MemoryCacheEntryOptions() { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(_seconds) }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /aspects/Cache/readme.md: -------------------------------------------------------------------------------- 1 | This package provides simple cache attribute implementation. 2 | Put ```[MemoryCache]``` attribure on your methods to easily cache them. 3 | 4 | [![Nuget](https://img.shields.io/nuget/v/Aspects.Cache?style=flat-square)](https://www.nuget.org/packages/Aspects.Cache/) 5 | 6 | It also works with **void** and **async** methods! 7 | 8 | ```c# 9 | class TestClass 10 | { 11 | [MemoryCache(3)] //cache results in memory for 3 sec 12 | public long Calculate(int a, string b) 13 | { 14 | return a + b.GetHashCode() + DateTime.Now.Ticks; 15 | } 16 | } 17 | 18 | public async Task Code() 19 | { 20 | var target = new TestCalss(); 21 | var expected = target.Calculate(10, "test"); 22 | await Task.Delay(10); 23 | var result = target.Calculate(10, "test"); 24 | 25 | Assert.Equal(expected, result); //equal because the result for input parameters is cached for 3 sec 26 | } 27 | ``` 28 | 29 | Feel free to implement your own cache mechanic by inheriting ```CacheAttribute``` class 30 | 31 | ```c# 32 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 33 | public class RedisCacheAttribute : CacheAttribute 34 | { 35 | ... 36 | } 37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /aspects/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | MIT 4 | true 5 | 6 | 7 | 8 | $(MSBuildThisFileDirectory)../src/AspectInjector/bin/$(Configuration)/netstandard2.0/AspectInjector.dll 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /aspects/Freezable/Aspects.Freezable.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | aspect freezable pattern attribute 5 | This package provides simple freezable pattern implementation. Put [Freezable] attribure on your properties or a whole class. Then cast your objects to IFreezable when needed. 6 | Powered by AspectInjector. 7 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Freezable 8 | 9 | -------------------------------------------------------------------------------- /aspects/Freezable/Freezable.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace Aspects.Freezable 6 | { 7 | [Aspect(Scope.PerInstance)] 8 | [Mixin(typeof(IFreezable))] 9 | [Injection(typeof(FreezableAttribute))] 10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false)] 11 | public class FreezableAttribute : Attribute, IFreezable 12 | { 13 | public bool IsFrozen { get; set; } 14 | 15 | public void Freeze() 16 | { 17 | IsFrozen = true; 18 | } 19 | 20 | [Advice(Kind.Before, Targets = Target.Public | Target.Setter)] 21 | [DebuggerHidden] 22 | [DebuggerStepThrough] 23 | public void CheckIfFrozen( 24 | [Argument(Source.Name)] string name, 25 | [Argument(Source.Type)] Type type 26 | ) 27 | { 28 | if (IsFrozen) 29 | throw new InvalidOperationException($"Attempt to modify '{type.Name}.{name}' of a frozen object."); 30 | } 31 | } 32 | 33 | public interface IFreezable 34 | { 35 | bool IsFrozen { get; } 36 | void Freeze(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /aspects/Freezable/readme.md: -------------------------------------------------------------------------------- 1 | This package provides simple freezable pattern implementation. 2 | Put ```[Freezable]``` attribure on your properties or a whole class. 3 | 4 | Then cast your objects to ```IFreezable``` when you need to make it frozen or check its status. 5 | 6 | ```c# 7 | [Freezable] 8 | class TestClass 9 | { 10 | public string Data { get; set; } 11 | } 12 | 13 | public void Code() 14 | { 15 | var target = new TestClass(); 16 | target.Data = "test1"; 17 | ((IFreezable)target).Freeze(); 18 | target.Data = "test2"; // <-- will throw exception 19 | } 20 | ``` -------------------------------------------------------------------------------- /aspects/Lazy/Aspects.Lazy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | aspect lazy attribute 6 | 7 | This package provides simple lazy aspect attribute. 8 | 9 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Lazy 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /aspects/Lazy/LazyAspect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Aspects.Lazy 7 | { 8 | [Aspect(Scope.PerInstance)] 9 | public sealed class LazyAspect 10 | { 11 | private readonly Dictionary _backFields = new Dictionary(); 12 | 13 | [Advice(Kind.Around, Targets = Target.Public | Target.Getter)] 14 | public object OnGet([Argument(Source.Target)] Func method, [Argument(Source.Type)] Type type, [Argument(Source.Name)] string name) 15 | { 16 | var key = GetKey(type.IsGenericType ? method.Method.DeclaringType : type, name); 17 | 18 | if (!_backFields.TryGetValue(key, out object value)) 19 | { 20 | lock (_backFields) 21 | { 22 | if (!_backFields.TryGetValue(key, out value)) 23 | { 24 | value = method(Array.Empty()); 25 | _backFields.Add(key, value); 26 | } 27 | } 28 | } 29 | 30 | return value; 31 | 32 | string GetKey(Type declareType, string targetName) 33 | { 34 | return $"{declareType.FullName}.{targetName}"; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /aspects/Lazy/LazyAttribute.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | 4 | namespace Aspects.Lazy 5 | { 6 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 7 | [Injection(typeof(LazyAspect))] 8 | public sealed class LazyAttribute : Attribute 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /aspects/Lazy/readme-cn.md: -------------------------------------------------------------------------------- 1 | [English](./readme.md) 2 | 3 | 这个包提供了类似 Lazy 的功能。 4 | 5 | 1. 假如有这样一个属性,并且你想延迟初始化: 6 | 7 | ```csharp 8 | class TestClass 9 | { 10 | public ServiceA ServiceA { get; } = new ServiceA(DateTime.Now); 11 | } 12 | ``` 13 | 14 | 2. 使用`LazyAttribute`,并将`ServiceA`定义为**只读属性**,如下: 15 | 16 | ```csharp 17 | class TestClass 18 | { 19 | [Lazy] 20 | public ServiceA ServiceA => new ServiceA(DateTime.Now); 21 | } 22 | ``` 23 | 24 | or 25 | 26 | ```csharp 27 | class TestClass 28 | { 29 | [Lazy] 30 | public ServiceA ServiceA 31 | { 32 | get { return new ServiceA(DateTime.Now); } 33 | } 34 | } 35 | ``` 36 | 37 | 3. 编译后的 DLL 实现似下面的效果: 38 | 39 | ```csharp 40 | class TestClass 41 | { 42 | private ServiceA _serviceA; 43 | 44 | [Lazy] 45 | public ServiceA ServiceA 46 | { 47 | get { return _serviceA ?? (_serviceA = new ServiceA(DateTime.Now)); } 48 | } 49 | } 50 | ``` 51 | 52 | 4. 但是`LazyAttribute`使用`Dictionary`缓存结果。 53 | 54 | 55 | -------------------------------------------------------------------------------- /aspects/Lazy/readme.md: -------------------------------------------------------------------------------- 1 | [中文](./readme-cn.md) 2 | 3 | This package provides Lazy-like functionality. 4 | 5 | 1. Suppose there is such a property and you want to delay the initialization: 6 | 7 | ```csharp 8 | class TestClass 9 | { 10 | public ServiceA ServiceA { get; } = new ServiceA(DateTime.Now); 11 | } 12 | ``` 13 | 14 | 2. Use `LazyAttribute`, and define `ServiceA` as a **read-only property**, as follows: 15 | 16 | ```csharp 17 | class TestClass 18 | { 19 | [Lazy] 20 | public ServiceA ServiceA => new ServiceA(DateTime.Now); 21 | } 22 | ``` 23 | 24 | or 25 | 26 | ```csharp 27 | class TestClass 28 | { 29 | [Lazy] 30 | public ServiceA ServiceA 31 | { 32 | get { return new ServiceA(DateTime.Now); } 33 | } 34 | } 35 | ``` 36 | 37 | 3. The compiled DLL such as: 38 | 39 | ```csharp 40 | class TestClass 41 | { 42 | private ServiceA _serviceA; 43 | 44 | [Lazy] 45 | public ServiceA ServiceA 46 | { 47 | get { return _serviceA ?? (_serviceA = new ServiceA(DateTime.Now)); } 48 | } 49 | } 50 | ``` 51 | 52 | 4. But `LazyAttribute` use a Dictionary caches fields. 53 | 54 | 55 | -------------------------------------------------------------------------------- /aspects/Logging/Aspects.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | aspect lazy attribute 5 | 6 | This package provides simple logging aspect attribute [Log]. 7 | 8 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Logging 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /aspects/Logging/LogAttribute.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace Aspects.Logging 6 | { 7 | [Injection(typeof(LoggingAspect))] 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event, AllowMultiple = false)] 9 | public class LogAttribute : Attribute 10 | { 11 | // Uses Stopwatch to measure execution time 12 | public bool MeasureTime { get; set; } = false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /aspects/Logging/LoggingAspect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | 7 | namespace Aspects.Logging 8 | { 9 | [Aspect(Scope.Global)] 10 | public class LoggingAspect 11 | { 12 | static LoggingAspect() 13 | { 14 | LoggerFactory = 15 | Microsoft.Extensions.Logging.LoggerFactory.Create(builder => 16 | builder.AddConsole()); 17 | } 18 | 19 | public static ILoggerFactory LoggerFactory { get; set; } 20 | 21 | [Advice(Kind.Around)] 22 | public object Around( 23 | [Argument(Source.Name)] string name, 24 | [Argument(Source.Arguments)] object[] args, 25 | [Argument(Source.Type)] Type hostType, 26 | [Argument(Source.Target)] Func target, 27 | [Argument(Source.Triggers)] Attribute[] triggers) 28 | { 29 | var trigger = triggers.OfType().First(); 30 | 31 | var logger = LoggerFactory.CreateLogger(hostType); 32 | logger.LogInformation("Executing method {method}", name); 33 | 34 | 35 | if (trigger.MeasureTime) 36 | { 37 | var sw = Stopwatch.StartNew(); 38 | 39 | var result = target(args); 40 | 41 | sw.Stop(); 42 | logger.LogInformation("Executed method {method} in {time} ms", name, sw.ElapsedMilliseconds); 43 | return result; 44 | } 45 | else 46 | { 47 | var result = target(args); 48 | 49 | logger.LogInformation("Executed method {method}", name); 50 | return result; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /aspects/Notify/Aspects.Notify.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | aspect notify notifypropertychanged INPC pattern attribute 5 | This package provides simple INotifyPropertyChanged aspect. Put [Notify] attribure on your properties or a whole class. You can use [NotifyAlso] attributes to notify other dependant properties. 6 | Powered by AspectInjector. 7 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Notify 8 | 9 | -------------------------------------------------------------------------------- /aspects/Notify/NotifyAspect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | 6 | namespace Aspects.Notify 7 | { 8 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] 9 | [Injection(typeof(NotifyAspect))] 10 | public class NotifyAttribute : Attribute 11 | { 12 | } 13 | 14 | [AttributeUsage(AttributeTargets.Property)] 15 | [Injection(typeof(NotifyAspect))] 16 | public class NotifyAlsoAttribute : Attribute 17 | { 18 | public NotifyAlsoAttribute(params string[] notifyAlso) => NotifyAlso = notifyAlso; 19 | public string[] NotifyAlso { get; } 20 | } 21 | 22 | 23 | [Mixin(typeof(INotifyPropertyChanged))] 24 | [Aspect(Scope.Global)] 25 | public class NotifyAspect : INotifyPropertyChanged 26 | { 27 | public event PropertyChangedEventHandler PropertyChanged = (s, e) => { }; 28 | 29 | [Advice(Kind.After, Targets = Target.Public | Target.Setter)] 30 | public void AfterSetter( 31 | [Argument(Source.Instance)] object source, 32 | [Argument(Source.Name)] string propName, 33 | [Argument(Source.Triggers)] Attribute[] triggers 34 | ) 35 | { 36 | if (triggers.OfType().Any()) 37 | PropertyChanged(source, new PropertyChangedEventArgs(propName)); 38 | 39 | foreach (var attr in triggers.OfType()) 40 | foreach (var additional in attr.NotifyAlso ?? new string[] { }) 41 | PropertyChanged(source, new PropertyChangedEventArgs(additional)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /aspects/Notify/readme.md: -------------------------------------------------------------------------------- 1 | This package provides simple INotifyPropertyChanged aspect. 2 | Put ```[Notify]``` attribure on your properties or a whole class. You can use ```[NotifyAlso]``` attributes to notify other dependant properties. 3 | 4 | [![Nuget](https://img.shields.io/nuget/v/Aspects.Notify?label=nuget&logo=nuget&style=flat-square)](https://www.nuget.org/packages/Aspects.Notify) 5 | 6 | ```c# 7 | class TestClass 8 | { 9 | [Notify] 10 | [NotifyAlso(nameof(FullName))] 11 | public string FirstName { get; set; } 12 | 13 | [Notify] 14 | [NotifyAlso(nameof(FullName))] 15 | public string LastName { get; set; } 16 | 17 | public string FullName => $"{FirstName} {LastName}"; 18 | } 19 | 20 | public void Code() 21 | { 22 | var target = new TestClass(); 23 | target.LastName = "Yu"; // <-- will raise ```PropertyChanged``` for ```LastName``` and ```FullName``` 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /aspects/Universal/Aspects.Universal.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | aspect proxy wrapper universal 6 | 7 | This package provides simple universal method wrrapper aspect. 8 | 9 | https://github.com/pamidur/aspect-injector/tree/master/samples/src/Universal 10 | 11 | 12 | -------------------------------------------------------------------------------- /aspects/Universal/Aspects/MethodWrapperAspect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using AspectInjector.Broker; 4 | 5 | namespace Aspects.Universal.Aspects 6 | { 7 | [Aspect(Scope.Global)] 8 | public class MethodWrapperAspect : BaseUniversalWrapperAspect 9 | { 10 | [Advice(Kind.Around, Targets = Target.Method)] 11 | public object Handle( 12 | [Argument(Source.Instance)] object instance, 13 | [Argument(Source.Type)] Type type, 14 | [Argument(Source.Metadata)] MethodBase method, 15 | [Argument(Source.Target)] Func target, 16 | [Argument(Source.Name)] string name, 17 | [Argument(Source.Arguments)] object[] args, 18 | [Argument(Source.ReturnType)] Type returnType, 19 | [Argument(Source.Triggers)] Attribute[] triggers) 20 | { 21 | return BaseHandle(instance, type, method, target, name, args, returnType, triggers); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /aspects/Universal/Aspects/PrivateMethodWrapperAspect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using AspectInjector.Broker; 4 | 5 | namespace Aspects.Universal.Aspects 6 | { 7 | [Aspect(Scope.Global)] 8 | public class PrivateMethodWrapperAspect : BaseUniversalWrapperAspect 9 | { 10 | [Advice(Kind.Around, Targets = Target.Private | Target.Method)] 11 | public object Handle( 12 | [Argument(Source.Instance)] object instance, 13 | [Argument(Source.Type)] Type type, 14 | [Argument(Source.Metadata)] MethodBase method, 15 | [Argument(Source.Target)] Func target, 16 | [Argument(Source.Name)] string name, 17 | [Argument(Source.Arguments)] object[] args, 18 | [Argument(Source.ReturnType)] Type returnType, 19 | [Argument(Source.Triggers)] Attribute[] triggers) 20 | { 21 | return BaseHandle(instance, type, method, target, name, args, returnType, triggers); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /aspects/Universal/Aspects/PublicMethodWrapperAspect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using AspectInjector.Broker; 4 | 5 | namespace Aspects.Universal.Aspects 6 | { 7 | [Aspect(Scope.Global)] 8 | public class PublicMethodWrapperAspect : BaseUniversalWrapperAspect 9 | { 10 | [Advice(Kind.Around, Targets = Target.Public | Target.Method)] 11 | public object Handle( 12 | [Argument(Source.Instance)] object instance, 13 | [Argument(Source.Type)] Type type, 14 | [Argument(Source.Metadata)] MethodBase method, 15 | [Argument(Source.Target)] Func target, 16 | [Argument(Source.Name)] string name, 17 | [Argument(Source.Arguments)] object[] args, 18 | [Argument(Source.ReturnType)] Type returnType, 19 | [Argument(Source.Triggers)] Attribute[] triggers) 20 | { 21 | return BaseHandle(instance, type, method, target, name, args, returnType, triggers); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /aspects/Universal/Aspects/PublicWrapperAspect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using AspectInjector.Broker; 4 | 5 | namespace Aspects.Universal.Aspects 6 | { 7 | [Aspect(Scope.Global)] 8 | public class PublicWrapperAspect : BaseUniversalWrapperAspect 9 | { 10 | [Advice(Kind.Around, Targets = Target.Public | Target.Any)] 11 | public object Handle( 12 | [Argument(Source.Instance)] object instance, 13 | [Argument(Source.Type)] Type type, 14 | [Argument(Source.Metadata)] MethodBase method, 15 | [Argument(Source.Target)] Func target, 16 | [Argument(Source.Name)] string name, 17 | [Argument(Source.Arguments)] object[] args, 18 | [Argument(Source.ReturnType)] Type returnType, 19 | [Argument(Source.Triggers)] Attribute[] triggers) 20 | { 21 | return BaseHandle(instance, type, method, target, name, args, returnType, triggers); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/BaseMethodPointsAspectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Aspects.Universal.Events; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | public abstract class BaseMethodPointsAspectAttribute : BaseUniversalWrapperAttribute 8 | { 9 | protected internal sealed override T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) 10 | { 11 | OnBefore(eventArgs); 12 | 13 | try 14 | { 15 | var result = base.WrapSync(target, args, eventArgs); 16 | OnAfter(eventArgs); 17 | 18 | return result; 19 | } 20 | catch (Exception exception) 21 | { 22 | return OnException(eventArgs, exception); 23 | } 24 | } 25 | 26 | protected internal sealed override async Task WrapAsync(Func> target, object[] args, AspectEventArgs eventArgs) 27 | { 28 | await OnBeforeAsync(eventArgs); 29 | 30 | try 31 | { 32 | var result = await target(args); 33 | await OnAfterAsync(eventArgs); 34 | 35 | return result; 36 | } 37 | catch (Exception exception) 38 | { 39 | return await OnExceptionAsync(eventArgs, exception); 40 | } 41 | } 42 | 43 | protected virtual void OnBefore(AspectEventArgs eventArgs) 44 | { 45 | } 46 | 47 | protected virtual void OnAfter(AspectEventArgs eventArgs) 48 | { 49 | } 50 | 51 | protected virtual T OnException(AspectEventArgs eventArgs, Exception exception) 52 | { 53 | throw exception; 54 | } 55 | 56 | protected virtual Task OnBeforeAsync(AspectEventArgs eventArgs) 57 | { 58 | OnBefore(eventArgs); 59 | return Task.CompletedTask; 60 | } 61 | 62 | protected virtual Task OnAfterAsync(AspectEventArgs eventArgs) 63 | { 64 | OnAfter(eventArgs); 65 | return Task.CompletedTask; 66 | } 67 | 68 | protected virtual Task OnExceptionAsync(AspectEventArgs eventArgs, Exception exception) 69 | { 70 | return Task.FromResult(OnException(eventArgs, exception)); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/BaseUniversalWrapperAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Aspects.Universal.Events; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | public abstract class BaseUniversalWrapperAttribute : Attribute 8 | { 9 | protected internal virtual T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) 10 | { 11 | return target(args); 12 | } 13 | protected internal virtual Task WrapAsync(Func> target, object[] args, AspectEventArgs eventArgs) 14 | { 15 | return target(args); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/MethodAspectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AspectInjector.Broker; 3 | using Aspects.Universal.Aspects; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 8 | [Injection(typeof(MethodWrapperAspect), Inherited = true)] 9 | public abstract class MethodAspectAttribute : BaseMethodPointsAspectAttribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/PrivateMethodAspectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AspectInjector.Broker; 3 | using Aspects.Universal.Aspects; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 8 | [Injection(typeof(PrivateMethodWrapperAspect), Inherited = true)] 9 | public abstract class PrivateMethodAspectAttribute : BaseMethodPointsAspectAttribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/PublicAspectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AspectInjector.Broker; 3 | using Aspects.Universal.Aspects; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 8 | [Injection(typeof(PublicWrapperAspect), Inherited = true)] 9 | public abstract class PublicAspectAttribute : BaseMethodPointsAspectAttribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /aspects/Universal/Attributes/PublicMethodAspectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AspectInjector.Broker; 3 | using Aspects.Universal.Aspects; 4 | 5 | namespace Aspects.Universal.Attributes 6 | { 7 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] 8 | [Injection(typeof(PublicMethodWrapperAspect), Inherited = true)] 9 | public abstract class PublicMethodAspectAttribute: BaseMethodPointsAspectAttribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /aspects/Universal/Events/AspectEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Aspects.Universal.Events 6 | { 7 | public class AspectEventArgs : EventArgs 8 | { 9 | public object Instance { get; internal set; } 10 | public Type Type { get; internal set; } 11 | public MethodBase Method { get; internal set; } 12 | public string Name { get; internal set; } 13 | public IReadOnlyList Args { get; internal set; } 14 | public Type ReturnType { get; internal set; } 15 | public Attribute[] Triggers { get; internal set; } 16 | } 17 | } -------------------------------------------------------------------------------- /aspects/readme.md: -------------------------------------------------------------------------------- 1 | ### Samples 2 | -------------------------------------------------------------------------------- /docs/advicearguments.md: -------------------------------------------------------------------------------- 1 | ## Aspect Injector Docs 2 | 3 | - [<- to contents...](readme.md) 4 | 5 | ### Advice Effect Arguments 6 | 7 | Advice method can accept various arguments that represent information about target and triggers and can be used in complex scenarios. 8 | 9 | #### Name 10 | 11 | **Name** argument carries a name of a target. If the target is a method, then the name will be the name of the method. If injection is applied to a whole property or an event then the name will be the name of the property or the event. 12 | 13 | ```c# 14 | class LogAspect { 15 | [Advice(Kind.Before, Targets = Target.Method)] 16 | public void LogEnter([Argument(Source.Name)] string name) 17 | { 18 | Console.WriteLine($"Entering method '{name}'."); 19 | } 20 | } 21 | ``` 22 | 23 | #### Type 24 | 25 | **Type** argument carries a type that contains the target method. 26 | 27 | ```c# 28 | class CountCreationAspect { 29 | private int _count = 0; 30 | [Advice(Kind.Before, Targets = Target.Constructor)] 31 | public void LogEnter([Argument(Source.Type)] Type type) 32 | { 33 | Console.WriteLine($"Instance of type {type.Name} created {++_count} times."); 34 | } 35 | } 36 | ``` 37 | 38 | #### Instance 39 | 40 | **Type** argument has a reference to the instance that owns target method. Note that the reference can be ```null``` when the target method is static. 41 | 42 | ```c# 43 | class FreezableAspect { 44 | [Advice(Kind.Before, Targets = Target.Public | Target.Setter)] 45 | public void CheckIfFrozen([Argument(Source.Instance)] object instance) 46 | { 47 | if (instance is IFreezable freezable && freezable.IsFrozen) 48 | throw new InvalidOperationException("Attempt to modify frozen object."); 49 | } 50 | } 51 | ``` 52 | 53 | #### Metadata (former Method) 54 | 55 | **Metadata** argument refers to reflection metadata of a target method. 56 | -------------------------------------------------------------------------------- /docs/aspect.md: -------------------------------------------------------------------------------- 1 | ## Aspect Injector Docs 2 | 3 | - [<- to contents...](readme.md) 4 | 5 | ### Defining Aspects 6 | 7 | One starts to define an Aspect by applying the ```[Aspect]``` attribute onto a given class. The class decorated with an aspect cannot be ```abstract```, ```static```, and generic. Otherwise you'll get an [error](errors/readme.md). 8 | 9 | ```c# 10 | [Aspect(Scope.Global)] 11 | class Log 12 | { 13 | } 14 | ``` 15 | 16 | There are currently two scopes in which aspect can operate: 17 | 18 | - **Scope.Global** - means aspect will operate as singleton. 19 | - **Scope.PerInstance** - means that every target's class will have its own instance of the aspect. Even if aspect is injected several times into different members of the class, still there will be the only instance of the aspect for this class. 20 | 21 | In addition, your aspect can be created either with a parameterless constructor or factory. Make sure the factory class has a method with the proper signature: 22 | 23 | ```c# 24 | [Aspect(Scope.Global, Factory = typeof(AspectFactory))] 25 | class Log 26 | { 27 | } 28 | 29 | class AspectFactory 30 | { 31 | public static object GetInstance(Type type) 32 | { 33 | } 34 | } 35 | ``` 36 | 37 | Next step is to define how your Aspect interacts with injection targets. You can achieve this by using [Mixin](mixin.md) and/or [Advice](advice.md) effects. 38 | -------------------------------------------------------------------------------- /docs/errors/AIAM001.md: -------------------------------------------------------------------------------- 1 | ### Error Message AIAM001 - Only interfaces are mixable 2 | -------------------------------------------------------------------------------- /docs/errors/AI_A000.md: -------------------------------------------------------------------------------- 1 | # AI_A000 - Aspect should contain effect 2 | --- 3 | This warning occures when there is an aspect class that does nothing - does not contain any effect. 4 | ```c# 5 | [Aspect(Scope.Global)] 6 | public class MyAspect 7 | { 8 | 9 | } 10 | ``` 11 | 12 | Consider adding an [Advice](../advice.md) or a [Mixin](../mixin.md) to your aspect. 13 | -------------------------------------------------------------------------------- /docs/errors/AI_A001.md: -------------------------------------------------------------------------------- 1 | # AI_A001 - Aspect must have valid signature 2 | --- 3 | This error occurs when the aspect class signature does not conform with the following rules: 4 | - Aspect must not be generic 5 | - Aspect must not be abstract 6 | - Aspect must not be static 7 | - Aspect must be public 8 | 9 | Example of valid aspect class definition: 10 | ```c# 11 | [Aspect(Scope.Global)] 12 | public class MyAspect 13 | { 14 | //advices 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/errors/AI_A002.md: -------------------------------------------------------------------------------- 1 | # AI_A002 - Aspect factory must contain factory method 2 | --- 3 | This error occurs when the aspect class uses Factory but Factory class does not have proper factory method. 4 | 5 | Example of valid aspect class definition and factory class definition: 6 | ```c# 7 | [Aspect(Scope.Global, Factory = typeof(MyFactory))] 8 | public class MyAspect 9 | { 10 | //advices 11 | } 12 | 13 | public class MyFactory 14 | { 15 | public static object GetInstance(Type type) 16 | { 17 | // return aspect instance 18 | } 19 | } 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/errors/AI_A003.md: -------------------------------------------------------------------------------- 1 | # AI_A003 - Aspect must have public parameterless constructor or factory 2 | --- 3 | This error occurs when the aspect class does not have parameterless constructor nand defined factory. 4 | Aspect class instances need a way to be instantinated. So they should either have parameterless constructor: 5 | ```c# 6 | [Aspect(Scope.Global)] 7 | public class MyAspect 8 | { 9 | public MyAspect() 10 | { 11 | } 12 | //advices 13 | } 14 | ``` 15 | ... or factory: 16 | ```c# 17 | [Aspect(Scope.Global, Factory = typeof(MyFactory))] 18 | public class MyAspect 19 | { 20 | public MyAspect(ILogger logger) 21 | { 22 | } 23 | //advices 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/errors/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pamidur/aspect-injector/2a4a154857f4389a96ff4a0421e97ff91e2e060d/docs/errors/readme.md -------------------------------------------------------------------------------- /docs/mixin.md: -------------------------------------------------------------------------------- 1 | ## Aspect Injector Docs 2 | 3 | - [<- to contents...](readme.md) 4 | 5 | ### Mixins 6 | 7 | **Mixins** is a powerfull feature that enables developers to add new logic/properties to an object by automatically implementing interfaces. 8 | Aspect containing a mixin will create members in target similar to interface's and proxy them back to the aspect. 9 | 10 | Consider scnerio where you want to add a property to a target: 11 | ```c# 12 | public class Target 13 | { 14 | public void Do() {} 15 | } 16 | ``` 17 | 18 | For this you need to create an interface that describes features you want to add: 19 | ```c# 20 | public interface IHaveProperty 21 | { 22 | string Data { get; set; } 23 | } 24 | ``` 25 | 26 | then we will create an aspect: 27 | ```c# 28 | [Aspect(Scope.Global)] 29 | [Injection(typeof(MyAspect))] 30 | [Mixin(typeof(IHaveProperty))] 31 | public class MyAspect: Attribute, IHaveProperty 32 | { 33 | public string Data { get; set; } 34 | } 35 | ``` 36 | 37 | And finally if apply this new aspect-attribute to `Target` will make look like (after compilation): 38 | ```c# 39 | [MyAspect] 40 | public class Target : IHaveProperty 41 | { 42 | string IHaveProperty.Data 43 | { 44 | get 45 | { 46 | return ((IHaveProperty)My1Aspect.__a$_instance).Data; 47 | } 48 | set 49 | { 50 | ((IHaveProperty)My1Aspect.__a$_instance).Data = value; 51 | } 52 | } 53 | 54 | public void Do() 55 | { 56 | } 57 | } 58 | ``` 59 | 60 | Note that it isn't necessary to apply aspect to `Target` class itself, it is enough to apply it any member, like this: 61 | ```c# 62 | public class Target 63 | { 64 | [MyAspect] 65 | public void Do() {} 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ## Aspect Injector Docs 2 | 3 | - [How it works](#this) _(on this page)_ 4 | - [Terminology](terminology.md) 5 | - [Defining Aspects](aspect.md) 6 | - [Injecting Aspects](injection.md) 7 | - [Advice Effect](advice.md) 8 | - [Advice Effect Arguments](advicearguments.md) 9 | - [Mixin Effect](mixin.md) 10 | 11 | ### How it works 12 | 13 | AspectInjector is a compile-time AOP framework which means that all required work is done at compile time. 14 | For example, before compilation your code looks like: 15 | 16 | ```c# 17 | [Aspect(Scope.Global)] 18 | [Injection(typeof(Log))] 19 | class Log : Attribute 20 | { 21 | [Advice(Kind.Before, Targets = Target.Method)] 22 | public void OnEntry([Argument(Source.Name)] string name) 23 | { 24 | Console.WriteLine($"Entering method {name}"); 25 | } 26 | } 27 | 28 | class TestClass 29 | { 30 | [Log] 31 | public void Do() 32 | { 33 | Console.WriteLine($"Done"); 34 | } 35 | } 36 | ``` 37 | 38 | Then when you hit F5, we pick up from there and change your assembly a bit, so it actually works like this: 39 | 40 | ```c# 41 | [Aspect(Scope.Global)] 42 | [Injection(typeof(Log))] 43 | class Log : Attribute 44 | { 45 | public static readonly Log __a$_instance; 46 | 47 | [Advice(Kind.Before, Targets = Target.Method)] 48 | public void OnEntry([Argument(Source.Name)] string name) 49 | { 50 | Console.WriteLine($"Entering method {name}"); 51 | } 52 | 53 | static Log() 54 | { 55 | __a$_instance = new Log(); 56 | } 57 | } 58 | 59 | internal class TestClass 60 | { 61 | [Log] 62 | public void Do() 63 | { 64 | Log.__a$_instance.OnEntry("Do"); 65 | Console.WriteLine("Done"); 66 | } 67 | } 68 | ``` 69 | 70 | Thus, there is no performance hit as often experienced with other runtime AOP frameworks. 71 | -------------------------------------------------------------------------------- /docs/terminology.md: -------------------------------------------------------------------------------- 1 | ## Aspect Injector Docs 2 | 3 | - [<- to contents...](readme.md) 4 | 5 | ### Terminology 6 | 7 | We're trying to use the same terminology as other frameworks. So, if you are familiar with those, you're familiar with Aspect Injector. 8 | 9 | - **Aspect** - class that encapsulates logic some logic. It has _Effects_ which are kind of entry points to aspect. [more](aspect.md) 10 | - **Effect** - formal description of how aspect interacts with other classes. There are currently two types of effect: _Advice_ and _Mixin_. Both can be present within single aspect together and many times. By applying deliberate combinations of effects, it allows for some complex scenarios. 11 | - **Advice** - a type of effect that describes how method is modified. It can be used to inject some code before, after and/ir instead of a method. [more](advice.md) 12 | - **Mixin** - a type of effect that describes how class is modified. It can be used to alter a class's implemented interfaces. [more](mixin.md) 13 | - **Trigger** - dotnet attribute that tells AspectInjector which aspect should be injected into target. [more](injection.md) 14 | - **Injection** (or Pointcut) - process of consumption of aspects. Aspect consumer can use injections to leverage aspect's logic. [more](injection.md) 15 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMinor" 5 | } 6 | } -------------------------------------------------------------------------------- /package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pamidur/aspect-injector/2a4a154857f4389a96ff4a0421e97ff91e2e060d/package.png -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AspectInjector1.Analyzer 6 | This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Analyzers/InjectionAttributeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Rules; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using System; 6 | using System.Collections.Immutable; 7 | using System.Linq; 8 | 9 | namespace AspectInjector.Analyzer.Analyzers 10 | { 11 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 12 | public class InjectionAttributeAnalyzer : DiagnosticAnalyzer 13 | { 14 | public override ImmutableArray SupportedDiagnostics 15 | => ImmutableArray.Create( 16 | InjectionRules.InjectionMustReferToAspect.AsDescriptor() 17 | , InjectionRules.InjectionMustBeAttribute.AsDescriptor() 18 | ); 19 | 20 | public override void Initialize(AnalysisContext context) 21 | { 22 | context.EnableConcurrentExecution(); 23 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); 24 | context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); 25 | } 26 | 27 | private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context) 28 | { 29 | var attr = context.ContainingSymbol.GetAttributes().FirstOrDefault(a => a.ApplicationSyntaxReference.Span == context.Node.Span); 30 | 31 | if (attr == null || attr.AttributeClass.ToDisplayString() != WellKnown.InjectionType) 32 | return; 33 | 34 | var location = context.Node.GetLocation(); 35 | 36 | var compilation = context.SemanticModel.Compilation; 37 | 38 | var attributeType = compilation.GetTypeByMetadataName(typeof(Attribute).FullName); 39 | 40 | if (context.ContainingSymbol is ITypeSymbol type) 41 | { 42 | if (type.TypeKind != TypeKind.Interface && !compilation.ClassifyConversion(type, attributeType).IsImplicit) 43 | context.ReportDiagnostic(Diagnostic.Create(InjectionRules.InjectionMustBeAttribute.AsDescriptor(), location, context.ContainingSymbol.Name)); 44 | } 45 | 46 | if (attr.AttributeConstructor == null) 47 | return; 48 | 49 | if (attr.ConstructorArguments[0].Value is INamedTypeSymbol arg) 50 | { 51 | if (arg.TypeKind == TypeKind.Error) 52 | return; 53 | 54 | if (!arg.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == WellKnown.AspectType)) 55 | context.ReportDiagnostic(Diagnostic.Create(InjectionRules.InjectionMustReferToAspect.AsDescriptor(), location, arg.Name)); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Analyzers/MixinAttributeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Rules; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using System.Collections.Immutable; 6 | using System.Linq; 7 | 8 | namespace AspectInjector.Analyzer.Analyzers 9 | { 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public class MixinAttributeAnalyzer : DiagnosticAnalyzer 12 | { 13 | public override ImmutableArray SupportedDiagnostics 14 | => ImmutableArray.Create( 15 | EffectRules.MixinSupportsOnlyInterfaces.AsDescriptor() 16 | , EffectRules.EffectMustBePartOfAspect.AsDescriptor() 17 | , EffectRules.MixinSupportsOnlyAspectInterfaces.AsDescriptor() 18 | ); 19 | 20 | public override void Initialize(AnalysisContext context) 21 | { 22 | context.EnableConcurrentExecution(); 23 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); 24 | context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); 25 | } 26 | 27 | private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context) 28 | { 29 | var attr = context.ContainingSymbol.GetAttributes().FirstOrDefault(a => a.ApplicationSyntaxReference.Span == context.Node.Span); 30 | 31 | if (attr == null || attr.AttributeClass.ToDisplayString() != WellKnown.MixinType) 32 | return; 33 | 34 | var location = context.Node.GetLocation(); 35 | 36 | if (!context.ContainingSymbol.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == WellKnown.AspectType)) 37 | context.ReportDiagnostic(Diagnostic.Create(EffectRules.EffectMustBePartOfAspect.AsDescriptor(), location, context.ContainingSymbol.Name)); 38 | 39 | if (attr.AttributeConstructor == null) 40 | return; 41 | 42 | if (attr.ConstructorArguments[0].Value is INamedTypeSymbol arg) 43 | { 44 | if (arg.TypeKind == TypeKind.Error) 45 | return; 46 | 47 | if (arg.TypeKind != TypeKind.Interface) 48 | context.ReportDiagnostic(Diagnostic.Create(EffectRules.MixinSupportsOnlyInterfaces.AsDescriptor(), location, arg.Name)); 49 | else if (context.ContainingSymbol is INamedTypeSymbol aspectClass && !aspectClass.AllInterfaces.Any(i => i == arg)) 50 | context.ReportDiagnostic(Diagnostic.Create(EffectRules.MixinSupportsOnlyAspectInterfaces.AsDescriptor(), location, ImmutableDictionary.Empty.Add(WellKnown.MixinTypeProperty, arg.Name), context.ContainingSymbol.Name, arg.Name)); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/AspectInjector.Analyzer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | Aspect Injector analyzers package. 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/CodeFixes/AspectCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Rules; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using System.Collections.Immutable; 8 | using System.Composition; 9 | using System.Linq; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace AspectInjector.Analyzer.CodeFixes 14 | { 15 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MixinCodeFixProvider)), Shared] 16 | public class AspectCodeFixProvider : CodeFixProvider 17 | { 18 | public sealed override ImmutableArray FixableDiagnosticIds => 19 | ImmutableArray.Create( 20 | AspectRules.AspectMustHaveValidSignature.Id 21 | ); 22 | 23 | 24 | public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) 25 | { 26 | var diagnostic = context.Diagnostics.First(); 27 | 28 | if (diagnostic.Id == AspectRules.AspectMustHaveValidSignature.Id) 29 | context.RegisterCodeFix(CodeAction.Create( 30 | title: $"Fix Aspect signature", 31 | createChangedDocument: c => RemoveModifier(context.Document, diagnostic.Location.SourceSpan.Start, c), 32 | equivalenceKey: diagnostic.Id), 33 | diagnostic); 34 | 35 | return Task.CompletedTask; 36 | } 37 | 38 | private async Task RemoveModifier(Document document, int from, CancellationToken ct) 39 | { 40 | var root = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false); 41 | var type = root.FindToken(from).Parent.AncestorsAndSelf().OfType().First(); 42 | 43 | var newtype = type; 44 | 45 | var statictoken = newtype.Modifiers.IndexOf(SyntaxKind.StaticKeyword); 46 | if (statictoken != -1) 47 | newtype = newtype.RemoveTokenKeepTrivia(newtype.Modifiers[statictoken]); 48 | 49 | var abstracttoken = newtype.Modifiers.IndexOf(SyntaxKind.AbstractKeyword); 50 | if (abstracttoken != -1) 51 | newtype = newtype.RemoveTokenKeepTrivia(newtype.Modifiers[abstracttoken]); 52 | 53 | if (newtype.TypeParameterList != null && !newtype.TypeParameterList.Span.IsEmpty) 54 | newtype = newtype.RemoveNode(newtype.TypeParameterList, SyntaxRemoveOptions.KeepExteriorTrivia); 55 | 56 | return document.WithSyntaxRoot(root.ReplaceNode(type, newtype)); 57 | } 58 | 59 | public override FixAllProvider GetFixAllProvider() 60 | { 61 | return WellKnownFixAllProviders.BatchFixer; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/CodeFixes/MixinCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Rules; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using System.Collections.Immutable; 8 | using System.Composition; 9 | using System.Linq; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace AspectInjector.Analyzer.CodeFixes 14 | { 15 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MixinCodeFixProvider)), Shared] 16 | public class MixinCodeFixProvider : CodeFixProvider 17 | { 18 | public sealed override ImmutableArray FixableDiagnosticIds 19 | { 20 | get { return ImmutableArray.Create(EffectRules.MixinSupportsOnlyAspectInterfaces.Id); } 21 | } 22 | 23 | public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) 24 | { 25 | var diagnostic = context.Diagnostics.First(); 26 | 27 | if (diagnostic.Id == EffectRules.MixinSupportsOnlyAspectInterfaces.Id) 28 | context.RegisterCodeFix(CodeAction.Create( 29 | title: $"Add interface '{diagnostic.Properties[WellKnown.MixinTypeProperty]}' to Aspect", 30 | createChangedDocument: c => ImplementInterface(context.Document, diagnostic, c), 31 | equivalenceKey: diagnostic.Id), 32 | diagnostic); 33 | 34 | return Task.CompletedTask; 35 | } 36 | 37 | private async Task ImplementInterface(Document document, Diagnostic diagnostic, CancellationToken ct) 38 | { 39 | var root = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false); 40 | var typeDecl = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 41 | var mixinType = diagnostic.Properties[WellKnown.MixinTypeProperty]; 42 | 43 | var iface = SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(mixinType)); 44 | 45 | var newclass = typeDecl 46 | .WithBaseList((typeDecl.BaseList ?? SyntaxFactory.BaseList()).AddTypes(iface).WithTrailingTrivia(typeDecl.BaseList?.GetTrailingTrivia() ?? typeDecl.Identifier.TrailingTrivia)) 47 | .WithIdentifier(typeDecl.Identifier.WithTrailingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "))); 48 | 49 | var newroot = root.ReplaceNode(typeDecl, newclass); 50 | 51 | return document.WithSyntaxRoot(newroot); 52 | } 53 | 54 | public override FixAllProvider GetFixAllProvider() 55 | { 56 | return WellKnownFixAllProviders.BatchFixer; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Extensions.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using System.Collections.Concurrent; 5 | 6 | namespace AspectInjector.Analyzer 7 | { 8 | public static class Extensions 9 | { 10 | private static readonly ConcurrentDictionary _descriptorCache = new ConcurrentDictionary(); 11 | 12 | public static TNode RemoveTokenKeepTrivia(this TNode node, SyntaxToken token) 13 | where TNode : SyntaxNode 14 | { 15 | var next = token.GetNextToken(); 16 | 17 | var newnode = node.ReplaceTokens(new[] { token, next }, (o, r) => o == token ? SyntaxFactory.Token(SyntaxKind.None) : next.WithLeadingTrivia(token.LeadingTrivia)); 18 | 19 | return newnode; 20 | } 21 | 22 | 23 | public static DiagnosticDescriptor AsDescriptor(this Rule rule) 24 | { 25 | if (!_descriptorCache.TryGetValue(rule, out var descriptor)) 26 | _descriptorCache[rule] = descriptor = new DiagnosticDescriptor(rule.Id, rule.Title, rule.Message, "Aspects", (DiagnosticSeverity)rule.Severity, true, rule.Description, rule.HelpLinkUri); 27 | 28 | return descriptor; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Refactorings/AdviceAttributeCodeRefactoringProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeRefactorings; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace AspectInjector.Analyzer.Refactorings 9 | { 10 | [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AdviceAttributeCodeRefactoringProvider))] 11 | public class AdviceAttributeCodeRefactoringProvider : AdviceCodeRefactoringProvider 12 | { 13 | public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) 14 | { 15 | var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); 16 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 17 | var node = root.FindNode(context.Span); 18 | 19 | var attrSyntax = (node as AttributeSyntax) ?? node.Parent as AttributeSyntax; 20 | 21 | if (attrSyntax != null) 22 | { 23 | var attr = semanticModel.GetSymbolInfo(attrSyntax, context.CancellationToken); 24 | if (attr.Symbol?.ContainingSymbol is INamedTypeSymbol named && named.ToDisplayString() == WellKnown.AdviceType) 25 | { 26 | var method = attrSyntax.AncestorsAndSelf().OfType().FirstOrDefault(); 27 | if (method != null) 28 | await RegisterRefactorings(context, method); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Refactorings/AspectAttributeCodeRefactoringProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeRefactorings; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace AspectInjector.Analyzer.Refactorings 9 | { 10 | [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AspectAttributeCodeRefactoringProvider))] 11 | public class AspectAttributeCodeRefactoringProvider : AspectCodeRefactoringProvider 12 | { 13 | public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) 14 | { 15 | var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); 16 | var root = await context.Document 17 | .GetSyntaxRootAsync(context.CancellationToken) 18 | .ConfigureAwait(false); 19 | var node = root.FindNode(context.Span); 20 | 21 | var attrSyntax = (node as AttributeSyntax) ?? node.Parent as AttributeSyntax; 22 | 23 | 24 | if (attrSyntax != null) 25 | { 26 | var attr = semanticModel.GetSymbolInfo(attrSyntax, context.CancellationToken); 27 | if (attr.Symbol?.ContainingSymbol is INamedTypeSymbol named && named.ToDisplayString() == WellKnown.AspectType) 28 | { 29 | var @class = attrSyntax.AncestorsAndSelf().OfType().FirstOrDefault(); 30 | if (@class != null) 31 | RegisterRefactoring(context, @class); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/Refactorings/AspectCodeRefactoringProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeActions; 3 | using Microsoft.CodeAnalysis.CodeRefactorings; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace AspectInjector.Analyzer.Refactorings 10 | { 11 | [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AspectCodeRefactoringProvider))] 12 | public class AspectCodeRefactoringProvider : CodeRefactoringProvider 13 | { 14 | public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) 15 | { 16 | var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); 17 | var root = await context.Document 18 | .GetSyntaxRootAsync(context.CancellationToken) 19 | .ConfigureAwait(false); 20 | var node = root.FindNode(context.Span); 21 | var @class = node as ClassDeclarationSyntax; 22 | if (@class == null) 23 | return; 24 | 25 | var attr = semanticModel.GetDeclaredSymbol(@class).GetAspectAttribute(); 26 | 27 | if (attr == null) 28 | return; 29 | 30 | RegisterRefactoring(context, @class); 31 | } 32 | 33 | protected void RegisterRefactoring(CodeRefactoringContext context, ClassDeclarationSyntax @class) 34 | { 35 | context.RegisterRefactoring(CodeAction.Create( 36 | "Add 'Before' advice to this aspect.", 37 | ct => 38 | CreateAdvice(context, @class, Samples.Advices.Before, ct))); 39 | context.RegisterRefactoring(CodeAction.Create( 40 | "Add 'After' advice to this aspect.", 41 | ct => 42 | CreateAdvice(context, @class, Samples.Advices.After, ct))); 43 | context.RegisterRefactoring(CodeAction.Create( 44 | "Add 'Around' advice to this aspect.", 45 | ct => 46 | CreateAdvice(context, @class, Samples.Advices.Around, ct))); 47 | } 48 | 49 | private async Task CreateAdvice(CodeRefactoringContext context, ClassDeclarationSyntax @class, (MethodDeclarationSyntax Method, UsingDirectiveSyntax[] Usings) advice, CancellationToken cancellationToken) 50 | { 51 | var root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); 52 | 53 | var newClass = @class.AddMembers(advice.Method); 54 | 55 | root = root.ReplaceNode(@class, newClass); 56 | root = root.WithUpdatedUsings(advice.Usings); 57 | 58 | return context.Document.WithSyntaxRoot(root); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/SyntaxBasicBlocks.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 4 | 5 | namespace AspectInjector.Analyzer 6 | { 7 | public static class SyntaxBasicBlocks 8 | { 9 | public static class Namespaces 10 | { 11 | public static readonly UsingDirectiveSyntax System = UsingDirective(IdentifierName("System")); 12 | public static readonly UsingDirectiveSyntax SystemReflection = UsingDirective(IdentifierName("System.Reflection")); 13 | public static readonly UsingDirectiveSyntax SystemLinq = UsingDirective(IdentifierName("System.Linq")); 14 | } 15 | 16 | public static class Types 17 | { 18 | public static readonly ArrayTypeSyntax ObjectArray = ArrayType(PredefinedType(Token(SyntaxKind.ObjectKeyword))) 19 | .AddRankSpecifiers(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression()))); 20 | public static readonly ArrayTypeSyntax AttributeArray = ArrayType(IdentifierName("Attribute")) 21 | .AddRankSpecifiers(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression()))); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AspectInjector.Analyzer/WellKnown.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace AspectInjector.Analyzer 6 | { 7 | internal static class WellKnown 8 | { 9 | public static readonly string InjectionType = typeof(Injection).FullName; 10 | 11 | public static readonly string AdviceType = typeof(Advice).FullName; 12 | public static readonly string AdviceArgumentType = typeof(Argument).FullName; 13 | public static readonly string MixinType = typeof(Mixin).FullName; 14 | public static readonly string AspectType = typeof(Aspect).FullName; 15 | 16 | public static readonly string MethodBase = typeof(MethodBase).FullName; 17 | 18 | public static readonly string Type = typeof(Type).FullName; 19 | public static readonly string Attribute = typeof(Attribute).FullName; 20 | 21 | public static readonly string MixinTypeProperty = "mixin_type"; 22 | public static readonly string AspectTypeProperty = "aspect_type"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Advice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Defines method as a special advice method. 7 | /// 8 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 9 | public sealed class Advice : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Specifies when advice method should be called. 15 | public Advice(Kind kind) 16 | { 17 | Kind = kind; 18 | } 19 | 20 | /// 21 | /// Specifies what target members this method is injected to. by default. 22 | /// 23 | public Target Targets { get; set; } = Target.Any; 24 | 25 | /// 26 | /// Kind of advice 27 | /// 28 | public Kind Kind { get; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Argument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Defines method parameter as a special advice parameter. 7 | /// 8 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 9 | public sealed class Argument : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Specifies source of advice argument. 15 | public Argument(Source source) 16 | { 17 | Source = source; 18 | } 19 | 20 | /// 21 | /// Advice argument source used to populate method parameter. 22 | /// 23 | public Source Source { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Aspect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Configures aspect usage scenarios. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 9 | public sealed class Aspect : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Scope in which aspect is instantiated. 15 | public Aspect(Scope scope) 16 | { 17 | Scope = scope; 18 | } 19 | 20 | /// 21 | /// Type that is used as aspect factory. Type should contain public static object GetInstance(Type) method. null represents original parameter-less constructor. 22 | /// 23 | public Type Factory { get; set; } 24 | /// 25 | /// Scope in which aspect is instantiated. 26 | /// 27 | public Scope Scope { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/AspectInjector.Broker/AspectInjector.Broker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | Broker for Aspect Injector operations. 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 7 | 8 | 1701,1702,S3376 9 | 10 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Injection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Marks attribute as an injection trigger. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] 9 | public class Injection : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Aspect to inject. 15 | public Injection(Type aspect) 16 | { 17 | Aspect = aspect; 18 | } 19 | 20 | /// 21 | /// Specifies priority for this cut. The higher priority the earlier execution. 22 | /// 23 | public ushort Priority { get; set; } 24 | /// 25 | /// Aspect type that is being injected. 26 | /// 27 | public Type Aspect { get; } = null; 28 | 29 | /// 30 | /// Specifies propagation strategy. | by default. 31 | /// 32 | public PropagateTo Propagation { get; set; } = PropagateTo.Members | PropagateTo.Types; 33 | 34 | /// 35 | /// Specifies subsequent members filter by name. The aspect will be applied to selected members as well. null means - all members. 36 | /// 37 | public string PropagationFilter { get; set; } 38 | 39 | /// 40 | /// Gets or sets a System.Boolean value that determines whether the indicated injection is inherited by derived attribute classes. 41 | /// 42 | public bool Inherited { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Kind.cs: -------------------------------------------------------------------------------- 1 | namespace AspectInjector.Broker 2 | { 3 | /// 4 | /// Advice method injection points enumeration. 5 | /// 6 | public enum Kind : byte 7 | { 8 | /// 9 | /// Advice method is called before target method. 10 | /// 11 | Before = 1, 12 | 13 | /// 14 | /// Advice method is called after target method. 15 | /// 16 | After = 2, 17 | 18 | /// 19 | /// Advice method is called instead of target method. Consider using and in order to make a subsequent call to target method. 20 | /// 21 | Around = 4 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Mixin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Mixes in an interface implementation trough creating proxy methods. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public sealed class Mixin : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Interface to implement. 15 | public Mixin(Type @interface) 16 | { 17 | Interface = @interface; 18 | } 19 | /// 20 | /// Interface injected into targets. 21 | /// 22 | public Type Interface { get; } 23 | } 24 | } -------------------------------------------------------------------------------- /src/AspectInjector.Broker/PropagateTo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// Proparation targets enumeration 7 | /// 8 | [Flags] 9 | public enum PropagateTo 10 | { 11 | /// 12 | /// Propagation is disabled 13 | /// 14 | None = 1, 15 | 16 | /// 17 | /// Propagate to methods 18 | /// 19 | Methods = 2, 20 | 21 | /// 22 | /// Propagate to properties 23 | /// 24 | Properties = 4, 25 | 26 | /// 27 | /// Propagate to events 28 | /// 29 | Events = 8, 30 | 31 | /// 32 | /// Propagate to all members 33 | /// 34 | Members = Methods + Properties + Events, 35 | 36 | /// 37 | /// Propagate to nested types. Causes recursive injection. 38 | /// 39 | Types = 16, 40 | 41 | /// 42 | /// Allows propagation to compiler generated members and types. Effectively allows propagation to lambdas and iterators. May affect performance. Use with caution! 43 | /// 44 | IncludeCompilerGenerated = 32, 45 | 46 | /// 47 | /// Propagate to everything. Could cause performance hit! 48 | /// 49 | Everything = Members + Types + IncludeCompilerGenerated 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace AspectInjector.Broker 2 | { 3 | /// 4 | /// Advice creation scope enumeration. 5 | /// 6 | public enum Scope : byte 7 | { 8 | /// 9 | /// Aspect is created and used as singleton. Default value. 10 | /// 11 | Global = 1, 12 | 13 | /// 14 | /// Instance of an aspect is created per target class instance. 15 | /// 16 | PerInstance = 2 17 | } 18 | } -------------------------------------------------------------------------------- /src/AspectInjector.Broker/SkipInjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspectInjector.Broker 4 | { 5 | /// 6 | /// When applied to a target, suppresses injection of any aspects to it. 7 | /// If applied to an interface or an interface method, suppresses injection to any of the corresponding implementations. 8 | /// 9 | [AttributeUsage( 10 | AttributeTargets.Class | 11 | AttributeTargets.Method | 12 | AttributeTargets.Constructor | 13 | AttributeTargets.Property | 14 | AttributeTargets.Event, 15 | AllowMultiple = false)] 16 | public sealed class SkipInjection : Attribute 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AspectInjector.Broker/Source.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | 5 | namespace AspectInjector.Broker 6 | { 7 | /// 8 | /// Advice argument sources enumeration. 9 | /// 10 | public enum Source : byte 11 | { 12 | /// 13 | /// Target's instance or null if target is static. 14 | /// Should be of type . 15 | /// 16 | Instance = 1, 17 | 18 | /// 19 | /// Target's owner. Usually class or struct type. 20 | /// Should be of type . 21 | /// 22 | Type = 2, 23 | 24 | /// 25 | /// Target method.- 26 | /// Should be of type . 27 | /// 28 | [Obsolete("Use Source.Metadata instead.")] 29 | [EditorBrowsable(EditorBrowsableState.Never)] 30 | Method = 3, 31 | 32 | /// 33 | /// Target method metadata. 34 | /// Should be of type . 35 | /// 36 | Metadata = 3, 37 | 38 | /// 39 | /// Target method delegate. Usage Target() for chaining methods. 40 | /// Should be of type . 41 | /// Works only with . 42 | /// 43 | Target = 4, 44 | 45 | /// 46 | /// Target name. 47 | /// Should be of type . 48 | /// 49 | Name = 5, 50 | 51 | /// 52 | /// Target method arguments. 53 | /// Should be of type []. 54 | /// 55 | Arguments = 6, 56 | 57 | /// 58 | /// Target method result. 59 | /// Should be of type . 60 | /// Works only with . 61 | /// 62 | ReturnValue = 7, 63 | 64 | /// 65 | /// Target method result type. 66 | /// Should be of type . 67 | /// 68 | ReturnType = 8, 69 | 70 | /// 71 | /// Set of injections that trigger this advice. 72 | /// Should be of type []. 73 | /// 74 | [Obsolete("Use Source.Triggers instead")] 75 | [EditorBrowsable(EditorBrowsableState.Never)] 76 | Injections = 9, 77 | 78 | /// 79 | /// Set of injections that trigger this advice. 80 | /// Should be of type []. 81 | /// 82 | Triggers = 9 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/AspectInjector.Core.Advice.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | This is Advice injection library of Aspect Injector. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace AspectInjector.Core.Advice 2 | { 3 | internal static class Constants 4 | { 5 | public static readonly string Prefix = Core.Constants.Prefix; 6 | public static readonly string AfterStateMachineMethodName = $"{Prefix}after_state_machine"; 7 | public static readonly string MovedThis = $"{Prefix}this"; 8 | public static readonly string MovedArgs = $"{Prefix}args"; 9 | } 10 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Effects/AdviceArgument.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using Mono.Cecil; 3 | 4 | namespace AspectInjector.Core.Advice.Effects 5 | { 6 | internal class AdviceArgument 7 | { 8 | public Source Source { get; set; } 9 | public ParameterDefinition Parameter { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Effects/AfterAdviceEffect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Core.Models; 3 | using FluentIL.Logging; 4 | using System.Linq; 5 | 6 | namespace AspectInjector.Core.Advice.Effects 7 | { 8 | internal class AfterAdviceEffect : BeforeAdviceEffect 9 | { 10 | public override Kind Kind => Kind.After; 11 | 12 | protected override void ValidateSupportedArguments(AspectDefinition aspectDefinition, ILogger log) 13 | { 14 | var wrongArgs = Arguments.Where(a => a.Source == Source.Target).ToArray(); 15 | LogWrongArgs(wrongArgs, aspectDefinition, log); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Effects/AroundAdviceEffect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Core.Models; 3 | using AspectInjector.Rules; 4 | using FluentIL; 5 | using FluentIL.Extensions; 6 | using FluentIL.Logging; 7 | using System.Linq; 8 | 9 | namespace AspectInjector.Core.Advice.Effects 10 | { 11 | internal class AroundAdviceEffect : AdviceEffectBase 12 | { 13 | public override Kind Kind => Kind.Around; 14 | 15 | public override bool Validate(AspectDefinition aspect, ILogger log) 16 | { 17 | var result = base.Validate(aspect, log); 18 | 19 | if (!Method.ReturnType.Match(Method.Module.TypeSystem.Object)) 20 | { 21 | log.Log(EffectRules.AdviceMustHaveValidSingnature, Method, Method.Name, EffectRules.Literals.MustBeObjectForAround); 22 | result = false; 23 | } 24 | 25 | return result; 26 | } 27 | 28 | protected override void ValidateSupportedArguments(AspectDefinition aspectDefinition, ILogger log) 29 | { 30 | var wrongArgs = Arguments.Where(a => a.Source == Source.ReturnValue).ToArray(); 31 | LogWrongArgs(wrongArgs, aspectDefinition, log); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Effects/BeforeAdviceEffect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Core.Models; 3 | using AspectInjector.Rules; 4 | using FluentIL; 5 | using FluentIL.Extensions; 6 | using FluentIL.Logging; 7 | 8 | namespace AspectInjector.Core.Advice.Effects 9 | { 10 | internal class BeforeAdviceEffect : AdviceEffectBase 11 | { 12 | public override Kind Kind => Kind.Before; 13 | 14 | public override bool Validate(AspectDefinition aspect, ILogger log) 15 | { 16 | var result = base.Validate(aspect, log); 17 | 18 | if (!Method.ReturnType.Match(Method.Module.TypeSystem.Void)) 19 | { 20 | log.Log(EffectRules.AdviceMustHaveValidSingnature, Method, Method.Name, EffectRules.Literals.MustBeVoidForInline); 21 | result = false; 22 | } 23 | 24 | return result; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Weavers/AdviceAroundWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Advice.Effects; 2 | using AspectInjector.Core.Advice.Weavers.Processes; 3 | using AspectInjector.Core.Models; 4 | using FluentIL.Logging; 5 | using Mono.Cecil; 6 | using System; 7 | 8 | namespace AspectInjector.Core.Advice.Weavers 9 | { 10 | public class AdviceAroundWeaver : AdviceInlineWeaver 11 | { 12 | public override byte Priority => 30; 13 | 14 | public AdviceAroundWeaver(ILogger logger) : base(logger) 15 | { 16 | } 17 | 18 | public override bool CanWeave(InjectionDefinition injection) 19 | { 20 | return injection.Effect is AroundAdviceEffect && 21 | (injection.Target is EventDefinition || injection.Target is PropertyDefinition || injection.Target is MethodDefinition); 22 | } 23 | 24 | protected override void WeaveMethod(MethodDefinition method, InjectionDefinition injection) 25 | { 26 | if (injection.Effect is AroundAdviceEffect) 27 | { 28 | var process = new AdviceAroundProcess(_log, method, injection); 29 | process.Execute(); 30 | } 31 | else 32 | { 33 | throw new NotSupportedException($"Unknown effect type. {injection.Effect?.GetType().Name}"); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Weavers/AdviceStateMachineWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Advice.Effects; 2 | using AspectInjector.Core.Advice.Weavers.Processes; 3 | using AspectInjector.Core.Models; 4 | using FluentIL.Extensions; 5 | using FluentIL.Logging; 6 | using Mono.Cecil; 7 | 8 | namespace AspectInjector.Core.Advice.Weavers 9 | { 10 | public class AdviceStateMachineWeaver : AdviceInlineWeaver 11 | { 12 | public override byte Priority => 40; 13 | 14 | public AdviceStateMachineWeaver(ILogger logger) : base(logger) 15 | { 16 | } 17 | 18 | public override bool CanWeave(InjectionDefinition injection) 19 | { 20 | var target = injection.Target as MethodDefinition; 21 | return injection.Effect is AfterAdviceEffect && target != null && (target.IsAsync() || target.IsIterator()); 22 | } 23 | 24 | protected override void WeaveMethod(MethodDefinition method, InjectionDefinition injection) 25 | { 26 | if (method.IsAsync()) 27 | new AfterAsyncWeaveProcess(_log, method, injection).Execute(); 28 | 29 | if (method.IsIterator()) 30 | new AfterIteratorWeaveProcess(_log, method, injection).Execute(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Weavers/Processes/AdviceAfterProcess.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Core.Advice.Effects; 3 | using AspectInjector.Core.Extensions; 4 | using AspectInjector.Core.Models; 5 | using FluentIL; 6 | using FluentIL.Extensions; 7 | using FluentIL.Logging; 8 | using Mono.Cecil; 9 | using Mono.Cecil.Cil; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | 13 | namespace AspectInjector.Core.Advice.Weavers.Processes 14 | { 15 | internal class AdviceAfterProcess : AdviceWeaveProcessBase 16 | { 17 | private static readonly Dictionary _returnVariablesCache = new Dictionary(); 18 | 19 | private readonly VariableDefinition _retvar; 20 | 21 | public AdviceAfterProcess(ILogger log, MethodDefinition target, InjectionDefinition injection) 22 | : base(log, target, injection) 23 | { 24 | if (!_method.ReturnType.Match(_method.Module.TypeSystem.Void) && _effect.Arguments.Any(a => a.Source == Source.ReturnValue)) 25 | _retvar = GetOrCreateRetVar(); 26 | } 27 | 28 | private VariableDefinition GetOrCreateRetVar() 29 | { 30 | if (!_returnVariablesCache.TryGetValue(_method.Body, out var ret)) 31 | { 32 | ret = new VariableDefinition(_method.ReturnType); 33 | _method.Body.Variables.Add(ret); 34 | _method.Body.InitLocals = true; 35 | 36 | _returnVariablesCache.Add(_method.Body, ret); 37 | } 38 | 39 | return ret; 40 | } 41 | 42 | public override void Execute() 43 | { 44 | _method.Body.BeforeExit( 45 | (in Cut exit) => 46 | { 47 | var cut = exit; 48 | if (_retvar != null) 49 | cut = cut.Store(_retvar); 50 | 51 | cut = cut 52 | .LoadAspect(_aspect) 53 | .Call(_effect.Method, LoadAdviceArgs); 54 | 55 | if (_retvar != null) 56 | cut = cut.Load(_retvar); 57 | 58 | return cut; 59 | }); 60 | } 61 | 62 | protected override Cut LoadReturnValueArgument(in Cut pc, AdviceArgument parameter) 63 | { 64 | if (_retvar == null) 65 | return pc.Null(); 66 | else 67 | return pc.Load(_retvar).Cast(_retvar.VariableType, pc.TypeSystem.Object); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Advice/Weavers/Processes/AdviceBeforeProcess.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Advice.Effects; 2 | using AspectInjector.Core.Extensions; 3 | using AspectInjector.Core.Models; 4 | using FluentIL; 5 | using FluentIL.Logging; 6 | using Mono.Cecil; 7 | 8 | namespace AspectInjector.Core.Advice.Weavers.Processes 9 | { 10 | internal class AdviceBeforeProcess : AdviceWeaveProcessBase 11 | { 12 | public AdviceBeforeProcess(ILogger log, MethodDefinition target, InjectionDefinition injection) 13 | : base(log, target, injection) 14 | { 15 | } 16 | 17 | public override void Execute() 18 | { 19 | _method.EnsureAspectInitialized(_aspect); 20 | _method.Body.OnAspectsInitialized( 21 | (in Cut e) => e 22 | .LoadAspect(_aspect) 23 | .Call(_effect.Method, LoadAdviceArgs) 24 | ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AspectInjector.Core.Mixin/AspectInjector.Core.Mixin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | This is Mixin injection library of Aspect Injector. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/AspectInjector.Core.Mixin/MixinEffect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Models; 2 | using AspectInjector.Rules; 3 | using FluentIL.Extensions; 4 | using FluentIL.Logging; 5 | using Mono.Cecil; 6 | 7 | namespace AspectInjector.Core.Mixin 8 | { 9 | internal class MixinEffect : Effect 10 | { 11 | public TypeReference InterfaceType { get; set; } 12 | 13 | public override bool IsApplicableFor(IMemberDefinition target) 14 | { 15 | return target is TypeDefinition || target is PropertyDefinition || target is EventDefinition || target is MethodDefinition; 16 | } 17 | 18 | public override bool Equals(Effect other) 19 | { 20 | if (!(other is MixinEffect effect)) 21 | return false; 22 | 23 | return effect.InterfaceType.Match(InterfaceType); 24 | } 25 | 26 | public override bool Validate(AspectDefinition aspect, ILogger log) 27 | { 28 | var result = true; 29 | 30 | if (!InterfaceType.Resolve().IsInterface) 31 | { 32 | log.Log(EffectRules.MixinSupportsOnlyInterfaces, aspect.Host, InterfaceType.Name); 33 | result = false; 34 | } 35 | 36 | if (!aspect.Host.Implements(InterfaceType)) 37 | { 38 | log.Log(EffectRules.MixinSupportsOnlyAspectInterfaces, aspect.Host, aspect.Host.Name, InterfaceType.Name); 39 | result = false; 40 | } 41 | 42 | return result; 43 | } 44 | 45 | public override string ToString() 46 | { 47 | return $"Mixin::{InterfaceType.Name}"; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Mixin/MixinReader.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Contracts; 2 | using AspectInjector.Core.Extensions; 3 | using AspectInjector.Core.Models; 4 | using Mono.Cecil; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace AspectInjector.Core.Mixin 9 | { 10 | public class MixinReader : IEffectReader 11 | { 12 | public IReadOnlyCollection Read(ICustomAttributeProvider host) 13 | { 14 | if (host is TypeDefinition source) 15 | return Extract(source); 16 | 17 | return new List(); 18 | } 19 | 20 | private IReadOnlyCollection Extract(TypeDefinition type) 21 | { 22 | var mixins = new List(); 23 | 24 | foreach (var ca in type.CustomAttributes.ToList()) 25 | { 26 | if (ca.AttributeType.FullName == WellKnownTypes.Mixin) 27 | mixins.Add(new MixinEffect { InterfaceType = ca.GetConstructorValue(0) }); 28 | } 29 | 30 | return mixins; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core.Mixin/MixinWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Contracts; 2 | using AspectInjector.Core.Models; 3 | using FluentIL.Logging; 4 | 5 | namespace AspectInjector.Core.Mixin 6 | { 7 | public class MixinWeaver : IEffectWeaver 8 | { 9 | private readonly ILogger _log; 10 | 11 | public MixinWeaver(ILogger log) => _log = log; 12 | 13 | public byte Priority => 10; 14 | 15 | public void Weave(InjectionDefinition injection) 16 | { 17 | var process = new MixinWeaveProcess(_log, injection.Target, injection.Source, (MixinEffect)injection.Effect); 18 | process.Execute(); 19 | } 20 | 21 | public bool CanWeave(InjectionDefinition injection) 22 | { 23 | return injection.Effect is MixinEffect mixin && mixin.IsApplicableFor(injection.Target); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/AspectInjector.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | This is core library of Aspect Injector. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AspectInjector.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace AspectInjector.Core 2 | { 3 | public static class Constants 4 | { 5 | public static readonly string Prefix = "__a$_"; 6 | public static readonly string AssetsResourceName = $"{Prefix}assets"; 7 | public static readonly string AspectGlobalField = $"{Prefix}instance"; 8 | public static readonly string AspectInstanceFieldPrefix = $"{Prefix}a_"; 9 | public static readonly string InstanceAspectsMethodName = $"{Prefix}initialize_aspects"; 10 | 11 | public static readonly string AspectFactoryMethodName = "GetInstance"; 12 | } 13 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Contracts/IAspectReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AspectInjector.Core.Models; 3 | using Mono.Cecil; 4 | 5 | namespace AspectInjector.Core.Contracts 6 | { 7 | public interface IAspectReader 8 | { 9 | IReadOnlyCollection ReadAll(AssemblyDefinition assembly); 10 | AspectDefinition Read(TypeDefinition type); 11 | } 12 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Contracts/IAspectWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Models; 2 | 3 | namespace AspectInjector.Core.Contracts 4 | { 5 | public interface IAspectWeaver 6 | { 7 | void WeaveGlobalAssests(AspectDefinition target); 8 | } 9 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Contracts/IEffectReader.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Models; 2 | using Mono.Cecil; 3 | using System.Collections.Generic; 4 | 5 | namespace AspectInjector.Core.Contracts 6 | { 7 | public interface IEffectReader 8 | { 9 | IReadOnlyCollection Read(ICustomAttributeProvider host); 10 | } 11 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Contracts/IEffectWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Models; 2 | 3 | namespace AspectInjector.Core.Contracts 4 | { 5 | public interface IEffectWeaver 6 | { 7 | byte Priority { get; } 8 | 9 | void Weave(InjectionDefinition injection); 10 | 11 | bool CanWeave(InjectionDefinition injection); 12 | } 13 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Contracts/IInjectionReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AspectInjector.Core.Models; 3 | using Mono.Cecil; 4 | 5 | namespace AspectInjector.Core.Contracts 6 | { 7 | public interface IInjectionReader 8 | { 9 | IReadOnlyCollection ReadAll(AssemblyDefinition assembly); 10 | } 11 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Extensions/CustomAttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Extensions; 2 | using Mono.Cecil; 3 | using System.Linq; 4 | 5 | namespace AspectInjector.Core.Extensions 6 | { 7 | public static class CustomAttributeExtensions 8 | { 9 | public static bool IsCompilerGenerated(this ICustomAttributeProvider t) 10 | { 11 | return t.CustomAttributes.Any(ca => ca.AttributeType.Match(WellKnownTypes.CompilerGeneratedAttribute)); 12 | } 13 | 14 | public static TR GetPropertyValue(this CustomAttribute ca, string name) 15 | { 16 | var prop = ca.Properties.Where(p => p.Name == name).ToList(); 17 | 18 | if (prop.Count != 1) 19 | return default; 20 | 21 | return (TR) prop[0].Argument.Value; 22 | } 23 | 24 | public static TR GetConstructorValue(this CustomAttribute ca, int argPosition) 25 | { 26 | if (argPosition + 1 > ca.ConstructorArguments.Count) 27 | return default; 28 | 29 | var prop = ca.ConstructorArguments[argPosition]; 30 | return (TR)prop.Value; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Models/Assets.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AspectInjector.Core.Models 4 | { 5 | internal class Assets 6 | { 7 | public List Aspects { get; set; } = new List(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Models/Effect.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Logging; 2 | using Mono.Cecil; 3 | using System; 4 | 5 | namespace AspectInjector.Core.Models 6 | { 7 | public abstract class Effect : IEquatable 8 | { 9 | public uint Priority { get; protected set; } 10 | 11 | public abstract bool Equals(Effect other); 12 | 13 | public override bool Equals(object obj) 14 | { 15 | return obj is Effect ef && Equals(ef); 16 | } 17 | 18 | public override int GetHashCode() 19 | { 20 | return GetType().GetHashCode(); 21 | } 22 | 23 | public abstract bool IsApplicableFor(IMemberDefinition target); 24 | 25 | public abstract bool Validate(AspectDefinition aspect, ILogger log); 26 | 27 | public override string ToString() 28 | { 29 | return GetType().Name; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Models/InjectionDefinition.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace AspectInjector.Core.Models 6 | { 7 | public sealed class InjectionDefinition : IEquatable 8 | { 9 | public IMemberDefinition Target { get; internal set; } 10 | 11 | public uint Priority { get; internal set; } 12 | 13 | public AspectDefinition Source { get; internal set; } 14 | 15 | public Effect Effect { get; internal set; } 16 | 17 | public List Triggers { get; internal set; } = new List(); 18 | 19 | public override bool Equals(object obj) 20 | { 21 | return obj is InjectionDefinition id && Equals(id); 22 | } 23 | 24 | public bool Equals(InjectionDefinition other) 25 | { 26 | return Source.Host.FullName == other.Source.Host.FullName 27 | && Target == other.Target 28 | && Effect.Equals(other.Effect); 29 | } 30 | 31 | public override int GetHashCode() 32 | { 33 | return Source.Host.FullName.GetHashCode(); 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return $"{Effect.ToString()} => {Target.MetadataToken.TokenType.ToString()} ::{Target.FullName}"; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/Services/AspectWeaver.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core.Contracts; 2 | using AspectInjector.Core.Extensions; 3 | using AspectInjector.Core.Models; 4 | using FluentIL; 5 | using FluentIL.Logging; 6 | using Mono.Cecil; 7 | using System.Linq; 8 | 9 | namespace AspectInjector.Core.Services 10 | { 11 | public class AspectWeaver : IAspectWeaver 12 | { 13 | private readonly ILogger _log; 14 | 15 | public AspectWeaver(ILogger logger) 16 | { 17 | _log = logger; 18 | } 19 | 20 | public void WeaveGlobalAssests(AspectDefinition target) 21 | { 22 | EnsureSingletonField(target); 23 | } 24 | 25 | private void EnsureSingletonField(AspectDefinition aspect) 26 | { 27 | var singletonField = aspect.Host.Fields.FirstOrDefault(m => m.Name == Constants.AspectGlobalField); 28 | 29 | if (singletonField == null) 30 | { 31 | singletonField = new FieldDefinition(Constants.AspectGlobalField, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly, aspect.Host); 32 | aspect.Host.Fields.Add(singletonField); 33 | 34 | var cctor = aspect.Host.Methods.FirstOrDefault(c => c.IsConstructor && c.IsStatic); 35 | 36 | if (cctor == null) 37 | { 38 | cctor = new MethodDefinition(".cctor", 39 | MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, 40 | aspect.Host.Module.TypeSystem.Void); 41 | 42 | aspect.Host.Methods.Add(cctor); 43 | 44 | cctor.Body.Instead((in Cut i) => i.Return()); 45 | } 46 | 47 | cctor.Body.AfterEntry((in Cut i) => i.Store(singletonField, (in Cut val) => val.CreateAspectInstance(aspect))); 48 | } 49 | 50 | aspect.Host.IsBeforeFieldInit = false; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/AspectInjector.Core/WellKnownTypes.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using FluentIL; 3 | using Mono.Cecil; 4 | using Mono.Cecil.Rocks; 5 | using System; 6 | using System.Diagnostics; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace AspectInjector.Core 10 | { 11 | public static class WellKnownTypes 12 | { 13 | public static readonly string Injection = typeof(Injection).FullName; 14 | public static readonly string Aspect = typeof(Aspect).FullName; 15 | public static readonly string Mixin = typeof(Mixin).FullName; 16 | public static readonly string Advice = typeof(Advice).FullName; 17 | public static readonly string Argument = typeof(Argument).FullName; 18 | public static readonly string SkipInjection = typeof(SkipInjection).FullName; 19 | 20 | public static readonly StandardType Attribute_Array = new StandardType("System", "Attribute", isArray: true); 21 | public static readonly StandardType Object_Array = new StandardType("System", "Object", isArray: true); 22 | public static readonly StandardType Object = new StandardType("System", "Object"); 23 | 24 | public static readonly StandardType CompilerGeneratedAttribute = new StandardType("System.Runtime.CompilerServices", "CompilerGeneratedAttribute"); 25 | public static readonly StandardType DebuggerHiddenAttribute = new StandardType("System.Diagnostics", "DebuggerHiddenAttribute", assemblyHints: new[] { "System.Diagnostics.Debug" }); 26 | public static readonly StandardType DebuggerStepThroughAttribute = new StandardType("System.Diagnostics", "DebuggerStepThroughAttribute", assemblyHints: new[] { "System.Diagnostics.Debug" }); 27 | 28 | public static readonly StandardType Func_ObjectArray_Object = new StandardType("System", "Func`2", elements: new[] { Object_Array, Object }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AspectInjector.Rules/AspectInjector.Rules.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/AspectInjector.Rules/AspectRules.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | 3 | namespace AspectInjector.Rules 4 | { 5 | public static class AspectRules 6 | { 7 | public static class Literals 8 | { 9 | public static readonly string HasGenericParams = "has generic parameters"; 10 | public static readonly string IsAbstract = "is abstract"; 11 | public static readonly string IsNotPublic = "is not public"; 12 | public static readonly string IsStatic = "is static"; 13 | } 14 | 15 | public static readonly Rule AspectShouldContainEffect = 16 | GeneralRules.Make("AI_A000", 17 | "Aspect should contain effect", 18 | "Aspect '{0}' does not have any effect", 19 | "Aspect should have at least one effect - Advice, Mixin etc.", 20 | RuleSeverity.Warning); 21 | 22 | public static readonly Rule AspectMustHaveValidSignature = 23 | GeneralRules.Make("AI_A001", 24 | "Aspect must not be generic, abstract or static. And it should be public.", 25 | "Aspect '{0}' {1}", 26 | "Aspect must have valid signature. Aspect must be non-generic, non-abstract and non-static public class."); 27 | 28 | public static readonly Rule AspectFactoryMustContainFactoryMethod = 29 | GeneralRules.Make("AI_A002", 30 | "Aspect factory must contain factory method", 31 | "Factory type '{0}' does not contain factory method", 32 | "Aspect factory must contain method 'public static object GetInstance(Type)'."); 33 | 34 | public static readonly Rule AspectMustHaveContructorOrFactory = 35 | GeneralRules.Make("AI_A003", 36 | "Aspect must have public parameterless constructor or factory", 37 | "Aspect '{0}' does not have public parameterless constructor nor defined factory", 38 | "Aspect must have public parameterless constructor or defined factory."); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AspectInjector.Rules/GeneralRules.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using System; 3 | 4 | namespace AspectInjector.Rules 5 | { 6 | public static class GeneralRules 7 | { 8 | internal static Rule Make(string id, string title, string message, string description, RuleSeverity severity = RuleSeverity.Error) 9 | => new Rule(id, title, message, severity, description: description, helpLinkUri: $"https://github.com/pamidur/aspect-injector/blob/master/docs/errors/{id}.md"); 10 | 11 | public static class Literals 12 | { 13 | public static readonly Func UnknownAdviceKind = k => $"Unknown advice kind '{k}'"; 14 | public static readonly Func UnknownAdviceTarget = t => $"Unknown advice target '{t}'"; 15 | public static readonly Func UnknownPropagationStrategy = t => $"Unknown propagation strategy '{t}'"; 16 | public static readonly Func InvalidPropagationFilter = t => $"Invalid propagation filter '{t}'"; 17 | public static readonly Func UnknownArgumentSource = s => $"Unknown argument source '{s}'"; 18 | public static readonly Func UnknownAspectScope = s => $"Unknown aspect scope '{s}'"; 19 | } 20 | 21 | public static readonly Rule Info = 22 | Make("AI_INFO", 23 | "Info message", 24 | "{0}", 25 | "Thanks for using https://github.com/pamidur/aspect-injector", 26 | RuleSeverity.Info); 27 | 28 | 29 | public static readonly Rule CompilationMustSecceedIfNoOtherErrors = 30 | Make("AI_ERR0", 31 | "Aspect Injector encounters error it wasn't expected", 32 | "{0}. Please submit an issue to https://github.com/pamidur/aspect-injector", 33 | "Aspect Injector encounters error it wasn't expected. Please submit an issue to https://github.com/pamidur/aspect-injector"); 34 | 35 | public static readonly Rule UnknownCompilationOption = 36 | Make("AI_ERR1", 37 | "Aspect Injector encounters unknown compilation option", 38 | "{0}. Please make sure your AspectInjector version is up to date.", 39 | "Aspect Injector encounters error it wasn't expected. If AspectInjector is up to date - please submit an issue to https://github.com/pamidur/aspect-injector"); 40 | 41 | public static readonly Rule UnexpectedCompilerBehaviour = 42 | Make("AI_ERR2", 43 | "Aspect Injector encounters unexpected behaviour", 44 | "{0}. Please submit an issue to https://github.com/pamidur/aspect-injector", 45 | "Aspect Injector encounters behavior it wasn't expected. Please submit an issue to https://github.com/pamidur/aspect-injector"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AspectInjector.Rules/InjectionRules.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | 3 | namespace AspectInjector.Rules 4 | { 5 | public static class InjectionRules 6 | { 7 | public static readonly Rule InjectionMustReferToAspect = 8 | GeneralRules.Make("AI_I000", 9 | "Injection must refer to Aspect type", 10 | "'{0}' is not an Aspect", 11 | "Non-aspect types cannot be injected."); 12 | 13 | public static readonly Rule InjectionMustBeAttribute = 14 | GeneralRules.Make("AI_I001", 15 | "Injection trigger must be an Attribute class or Interface", 16 | "'{0}' is not an Attribute or interface", 17 | "Injection Attribute can only mark an attribute class or interface."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AspectInjector/AspectInjectorTask.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using Microsoft.Build.Framework; 4 | 5 | namespace AspectInjector; 6 | 7 | public class AspectInjectorTask : Microsoft.Build.Utilities.Task 8 | { 9 | [Required] 10 | public required string AssemblyPath { get; init; } 11 | public ITaskItem[] References { get; set; } = []; 12 | 13 | public bool AttachDebugger { get; init; } 14 | public bool Optimize { get; init; } 15 | public bool Verbose { get; init; } 16 | 17 | public override bool Execute() 18 | { 19 | if (AttachDebugger) AttachDebuggerNow(); 20 | return new Compiler().Execute(AssemblyPath, References.Select(r=>r.GetMetadata("FullPath")).ToArray(), Optimize, Verbose, Log); 21 | } 22 | 23 | private static void AttachDebuggerNow() 24 | { 25 | Console.WriteLine("DEBUG MODE!!! Waiting 10 sec for debugger to attach!"); 26 | Console.WriteLine($"Process id is '{Process.GetCurrentProcess().Id}'"); 27 | Debugger.Launch(); 28 | var c = 0; 29 | while (!Debugger.IsAttached && c < 20) 30 | { 31 | Thread.Sleep(1000); 32 | Console.Write("."); 33 | c++; 34 | } 35 | 36 | Console.WriteLine(); 37 | 38 | if (Debugger.IsAttached) 39 | { 40 | Console.WriteLine("Debugger attached."); 41 | Debugger.Break(); 42 | } 43 | else 44 | { 45 | Console.WriteLine("Debugger is not attached."); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AspectInjector/Compiler.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Core; 2 | using AspectInjector.Core.Advice; 3 | using AspectInjector.Core.Advice.Weavers; 4 | using AspectInjector.Core.Mixin; 5 | using AspectInjector.Core.Services; 6 | using AspectInjector.Rules; 7 | using FluentIL.Logging; 8 | using Microsoft.Build.Utilities; 9 | 10 | namespace AspectInjector; 11 | 12 | public class Compiler 13 | { 14 | public bool Execute(string filename, IReadOnlyList references, bool optimize, bool verbose, TaskLoggingHelper logHelper) 15 | { 16 | #pragma warning disable S1854 // Unused assignments should be removed 17 | var version = typeof(Compiler).Assembly.GetName().Version.ToString(3); 18 | #pragma warning restore S1854 // Unused assignments should be removed 19 | 20 | #if DEBUG 21 | version = "DEV"; 22 | #endif 23 | 24 | var app = $"AspectInjector|{version}"; 25 | var log = new MsBuildLogger(logHelper, verbose); 26 | log.Log(GeneralRules.Info, app); 27 | 28 | try 29 | { 30 | var processor = CreateApp(log); 31 | processor.Process(filename, references, optimize, verbose); 32 | } 33 | catch (Exception e) 34 | { 35 | log.Log(GeneralRules.CompilationMustSecceedIfNoOtherErrors, $"Processing failure: {e}"); 36 | } 37 | 38 | return !log.IsErrorThrown; 39 | } 40 | 41 | private Processor CreateApp(ILogger logger) 42 | { 43 | var aspectReader = new AspectReader([ 44 | new MixinReader(), 45 | new AdviceReader(logger) 46 | ], logger); 47 | var processor = new Processor(aspectReader, new InjectionReader(aspectReader, logger), new AspectWeaver(logger), [ 48 | new MixinWeaver(logger), 49 | new AdviceInlineWeaver(logger), 50 | new AdviceAroundWeaver(logger), 51 | new AdviceStateMachineWeaver(logger) 52 | ], logger); 53 | 54 | return processor; 55 | } 56 | } -------------------------------------------------------------------------------- /src/AspectInjector/MsBuildLogger.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | using Mono.Cecil.Cil; 5 | 6 | namespace AspectInjector; 7 | 8 | public class MsBuildLogger(TaskLoggingHelper log, bool verbose) : FluentIL.Logging.ILogger 9 | { 10 | public virtual bool IsErrorThrown { get; private set; } 11 | 12 | public void Log(Rule rule, SequencePoint sp, params string[] parameters) 13 | { 14 | parameters ??= []; 15 | 16 | switch (rule.Severity) 17 | { 18 | case RuleSeverity.Error: 19 | IsErrorThrown = true; 20 | if (sp?.Document == null) 21 | { 22 | log.LogError(rule.Message, parameters); 23 | } 24 | else 25 | { 26 | log.LogError( 27 | null, rule.Id, rule.Description, rule.HelpLinkUri, 28 | sp.Document.Url, sp.StartLine, sp.StartColumn, sp.EndLine, sp.EndColumn, 29 | rule.Message, parameters); 30 | } 31 | break; 32 | case RuleSeverity.Warning: 33 | if (sp?.Document == null) 34 | { 35 | log.LogWarning(rule.Message, parameters); 36 | } 37 | else 38 | { 39 | log.LogWarning( 40 | null, rule.Id, rule.Description, rule.HelpLinkUri, 41 | sp.Document.Url, sp.StartLine, sp.StartColumn, sp.EndLine, sp.EndColumn, 42 | rule.Message, parameters); 43 | } 44 | break; 45 | case RuleSeverity.Info: 46 | if (sp?.Document == null) 47 | { 48 | log.LogMessage( 49 | verbose ? MessageImportance.High : MessageImportance.Normal, 50 | rule.Message, parameters); 51 | } 52 | else 53 | { 54 | log.LogMessage( 55 | null, rule.Id, rule.Description, 56 | sp.Document.Url, sp.StartLine, sp.StartColumn, sp.EndLine, sp.EndColumn, 57 | verbose ? MessageImportance.High : MessageImportance.Normal, 58 | rule.Message, parameters); 59 | } 60 | break; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AspectInjector/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not install analyzers via install.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | if (Test-Path $analyzersPath) 17 | { 18 | # Install the language agnostic analyzers. 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Install language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/AspectInjector/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | # Uninstall the language agnostic analyzers. 17 | if (Test-Path $analyzersPath) 18 | { 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Uninstall language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | try 55 | { 56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 57 | } 58 | catch 59 | { 60 | 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/FluentIL.Common/FluentIL.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/FluentIL.Common/Rule.cs: -------------------------------------------------------------------------------- 1 | namespace FluentIL.Common 2 | { 3 | public enum RuleSeverity 4 | { 5 | Hidden, 6 | Info, 7 | Warning, 8 | Error 9 | } 10 | 11 | public class Rule 12 | { 13 | 14 | public Rule(string id, string title, string message, RuleSeverity severity, string description, string helpLinkUri) 15 | { 16 | Id = id; 17 | Title = title; 18 | Message = message; 19 | Severity = severity; 20 | Description = description; 21 | HelpLinkUri = helpLinkUri; 22 | } 23 | 24 | public string Id { get; } 25 | public string Title { get; } 26 | public string Message { get; } 27 | public RuleSeverity Severity { get; } 28 | public string Description { get; } 29 | public string HelpLinkUri { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/FluentIL/Cuts/Statements.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Extensions; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using System; 5 | 6 | namespace FluentIL 7 | { 8 | public static class Statements 9 | { 10 | public static Cut Return(this in Cut cut) 11 | { 12 | return cut.Write(OpCodes.Ret); 13 | } 14 | 15 | public static Cut Call(this in Cut cut, MethodReference method, PointCut args = null) 16 | { 17 | var cur_cut = cut; 18 | 19 | if (!method.IsCallCompatible()) 20 | throw new ArgumentException($"Uninitialized generic call reference: {method}"); 21 | 22 | if (args != null) cur_cut = cur_cut.Here(args); 23 | 24 | var methodDef = method.Resolve(); 25 | 26 | var code = OpCodes.Call; 27 | if (methodDef.IsConstructor) code = OpCodes.Newobj; 28 | else if (methodDef.IsVirtual) code = OpCodes.Callvirt; 29 | 30 | return cur_cut.Write(code, method); 31 | } 32 | 33 | public static Cut IfEqual(this in Cut pc, PointCut left, PointCut right, PointCut pos = null, PointCut neg = null) 34 | { 35 | if (pos != null && neg != null) 36 | return Compare(pc, left, right, OpCodes.Ceq, pos, neg); 37 | 38 | if (pos != null) 39 | return Compare(pc, left, right, OpCodes.Ceq, OpCodes.Brfalse, pos); 40 | 41 | if (neg != null) 42 | return Compare(pc, left, right, OpCodes.Ceq, OpCodes.Brtrue, neg); 43 | 44 | return pc; 45 | } 46 | 47 | private static Cut Compare(in Cut cut, PointCut left, PointCut right, OpCode cmp, PointCut pos, PointCut neg) 48 | { 49 | var pc = cut 50 | .Here(left) 51 | .Here(right) 52 | .Write(cmp); 53 | 54 | var pe = pc.Here(pos); 55 | var ne = pe.Here(neg); 56 | 57 | var exit = ne.Write(OpCodes.Nop); 58 | 59 | pc.Write(OpCodes.Brfalse, pe.Next()); 60 | pe.Write(OpCodes.Br, exit); 61 | 62 | return exit; 63 | } 64 | 65 | private static Cut Compare(in Cut cut, PointCut left, PointCut right, OpCode cmp, OpCode brexit, PointCut action) 66 | { 67 | var pc = cut 68 | .Here(left) 69 | .Here(right) 70 | .Write(cmp); 71 | 72 | var exit = pc.Write(OpCodes.Nop); 73 | 74 | pc.Write(brexit, exit).Here(action); 75 | 76 | return exit; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/FluentIL/Extensions/CecilReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace FluentIL.Extensions 6 | { 7 | public static class CecilReferenceExtensions 8 | { 9 | public static bool Match(this TypeReference tr1, TypeReference tr2) 10 | { 11 | if (tr1 == null || tr2 == null) 12 | return false; 13 | 14 | if (tr1 == tr2) return true; 15 | 16 | return tr1.FullName == tr2.FullName; 17 | } 18 | 19 | public static bool Match(this TypeReference tr1, StandardType type) 20 | { 21 | if (tr1 == null || type == null) 22 | return false; 23 | 24 | return tr1.FullName == type.ToString(); 25 | } 26 | 27 | public static bool Implements(this TypeReference tr, TypeReference @interface) 28 | { 29 | var td = tr.Resolve(); 30 | var ti = @interface; 31 | 32 | return td.Interfaces.Any(i => i.InterfaceType.Match(ti)) || (td.BaseType != null && td.BaseType.Implements(ti)); 33 | } 34 | 35 | public static bool IsAsync(this MethodDefinition m) 36 | { 37 | return m.CustomAttributes.Any(a => a.AttributeType.Match(StandardType.AsyncStateMachineAttribute)); 38 | } 39 | 40 | public static bool IsIterator(this MethodDefinition m) 41 | { 42 | return m.CustomAttributes.Any(a => a.AttributeType.Match(StandardType.IteratorStateMachineAttribute)); 43 | } 44 | 45 | public static bool IsUnsafe(this MethodDefinition m) 46 | { 47 | return m.ReturnType.IsPointer || m.Parameters.Any(p => p.ParameterType.IsPointer); 48 | } 49 | 50 | public static bool IsNormalMethod(this MethodDefinition m) 51 | { 52 | return !m.IsAddOn && !m.IsRemoveOn && !m.IsSetter && !m.IsGetter && !m.IsConstructor; 53 | } 54 | 55 | public static bool IsExplicitImplementationOf(this MethodDefinition m, MethodReference ifaceMethod) 56 | { 57 | if (m.Overrides.Any(o => o.FullName == ifaceMethod.FullName)) 58 | return true; 59 | 60 | return false; 61 | } 62 | 63 | public static TypeReference GetEnumType(this TypeDefinition enumtype) 64 | { 65 | if (!enumtype.IsEnum) 66 | throw new InvalidOperationException($"{enumtype.Name} is not enum"); 67 | 68 | return enumtype.Fields.First(f => f.Name == "value__").FieldType; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/FluentIL/Extensions/GenericsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Collections.Generic; 3 | 4 | namespace FluentIL.Extensions 5 | { 6 | public static class GenericsExtensions 7 | { 8 | public static void CloneTo(this Collection from, IGenericParameterProvider to) 9 | { 10 | foreach (var from_param in from) 11 | { 12 | to.GenericParameters.Add(new GenericParameter(from_param.Name, to) 13 | { 14 | Attributes = from_param.Attributes, 15 | IsValueType = from_param.IsValueType 16 | }); 17 | } 18 | 19 | foreach (var from_param in from) 20 | { 21 | var to_param = to.GenericParameters[from_param.Position]; 22 | foreach (var from_constraint in from_param.Constraints) 23 | { 24 | to_param.Constraints.Add(new GenericParameterConstraint(to.Module.ImportReference(from_constraint.ConstraintType, to))); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/FluentIL/Extensions/PointCutExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Mono.Cecil.Cil; 3 | 4 | namespace FluentIL.Extensions 5 | { 6 | public static class PointCutExtensions 7 | { 8 | public static Cut Write(this in Cut pc, OpCode opCode, object operand) => pc.Write(pc.Emit(opCode, operand)); 9 | public static Cut Write(this in Cut pc, OpCode opCode) => pc.Write(pc.Emit(opCode)); 10 | 11 | public static Cut Replace(this in Cut pc, OpCode opCode, object operand) => pc.Replace(pc.Emit(opCode, operand)); 12 | public static Cut Replace(this in Cut pc, OpCode opCode) => pc.Replace(pc.Emit(opCode)); 13 | 14 | public static TypeReference Import(this in Cut cut, TypeReference tr) => cut.Method.Module.ImportReference(tr); 15 | public static TypeReference Import(this in Cut cut, StandardType st) => cut.Method.Module.ImportStandardType(st); 16 | public static MethodReference Import(this in Cut cut, MethodReference mr) => cut.Method.Module.ImportReference(mr); 17 | public static FieldReference Import(this in Cut cut, FieldReference fr) => cut.Method.Module.ImportReference(fr); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/FluentIL/FluentIL.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/FluentIL/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using Mono.Cecil.Cil; 3 | 4 | namespace FluentIL.Logging 5 | { 6 | public interface ILogger 7 | { 8 | bool IsErrorThrown { get; } 9 | void Log(Rule rule, SequencePoint sp, params string[] messages); 10 | } 11 | } -------------------------------------------------------------------------------- /src/FluentIL/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using Mono.Cecil.Cil; 3 | using System; 4 | 5 | namespace FluentIL.Logging 6 | { 7 | public class ConsoleLogger : ILogger 8 | { 9 | private readonly string _toolName; 10 | 11 | public ConsoleLogger(string toolName) 12 | { 13 | _toolName = toolName; 14 | } 15 | 16 | public virtual bool IsErrorThrown { get; private set; } 17 | 18 | public void Log(Rule rule, SequencePoint sp, params string[] messages) 19 | { 20 | var location = sp?.Document == null ? _toolName : 21 | $"{sp.Document.Url}({sp.StartLine},{sp.StartColumn},{sp.EndLine},{sp.EndColumn})"; 22 | 23 | var message = string.Format(rule.Message.ToString(), messages ?? new string[] { }); 24 | 25 | switch (rule.Severity) 26 | { 27 | case RuleSeverity.Error: WriteError(rule.Id, location, message); IsErrorThrown = true; break; 28 | case RuleSeverity.Warning: WriteWarning(rule.Id, location, message); break; 29 | case RuleSeverity.Info: WriteInfo(location, message); break; 30 | } 31 | } 32 | 33 | private void WriteInfo(string location, string message) 34 | { 35 | Console.WriteLine($"{location}: {message}"); 36 | } 37 | 38 | private void WriteWarning(string id, string location, string message) 39 | { 40 | Console.WriteLine($"{location}: warning {id}: {message}"); 41 | } 42 | 43 | private void WriteError(string id, string location, string message) 44 | { 45 | Console.WriteLine($"{location}: error {id}: {message}"); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/FluentIL/Logging/LoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentIL.Common; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using System.Linq; 5 | 6 | namespace FluentIL.Logging 7 | { 8 | public static class LoggerExtensions 9 | { 10 | public static void Log(this ILogger log, Rule rule, params string[] messages) 11 | { 12 | log.Log(rule, null, messages); 13 | } 14 | 15 | public static void Log(this ILogger log, Rule rule, ICustomAttributeProvider ap, params string[] messages) 16 | { 17 | if (ap is IMemberDefinition md) Log(log, rule, md, messages); 18 | else log.Log(rule, null, messages); 19 | } 20 | 21 | public static void Log(this ILogger log, Rule rule, IMemberDefinition md, params string[] messages) 22 | { 23 | switch (md) 24 | { 25 | case MethodDefinition method: Log(log, rule, method, messages); break; 26 | case TypeDefinition type: Log(log, rule, type, messages); break; 27 | 28 | default: log.Log(rule, null, messages); break; 29 | } 30 | } 31 | 32 | public static void Log(this ILogger log, Rule rule, MethodDefinition md, params string[] messages) 33 | { 34 | log.Log(rule, GetSPFromMethod(md), messages); 35 | } 36 | 37 | public static void Log(this ILogger log, Rule rule, TypeDefinition td, params string[] messages) 38 | { 39 | var sp = td.Methods.Select(GetSPFromMethod).FirstOrDefault(s => s != null); 40 | log.Log(rule, sp, messages); 41 | } 42 | 43 | private static SequencePoint GetSPFromMethod(MethodDefinition md) 44 | { 45 | return md.DebugInformation.SequencePoints.FirstOrDefault(s => s.Document != null); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/FluentIL/Resolvers/CachedAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace FluentIL.Resolvers 6 | { 7 | public class CachedAssemblyResolver : BaseAssemblyResolver 8 | { 9 | private readonly Dictionary _cache = new Dictionary(); 10 | 11 | public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) 12 | { 13 | var result = _cache.ContainsKey(name.FullName) ? _cache[name.FullName] : null; 14 | 15 | if (result == null) 16 | { 17 | result = LookupAssembly(name, parameters); 18 | _cache[name.FullName] = result; 19 | } 20 | 21 | return result; 22 | } 23 | 24 | protected virtual AssemblyDefinition LookupAssembly(AssemblyNameReference name, ReaderParameters parameters) 25 | { 26 | return base.Resolve(name, parameters); 27 | } 28 | 29 | protected internal void RegisterAssembly(AssemblyDefinition assembly) 30 | { 31 | if (assembly == null) 32 | throw new ArgumentNullException("assembly"); 33 | 34 | var name = assembly.Name.FullName; 35 | if (_cache.ContainsKey(name)) 36 | return; 37 | 38 | _cache[name] = assembly; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/FluentIL/Resolvers/KnownReferencesAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace FluentIL.Resolvers 7 | { 8 | 9 | public class KnownReferencesAssemblyResolver : CachedAssemblyResolver 10 | { 11 | private readonly List _references = new List(); 12 | 13 | public void AddReference(string assemblyPath) 14 | { 15 | _references.Add(assemblyPath); 16 | } 17 | 18 | protected override AssemblyDefinition LookupAssembly(AssemblyNameReference name, ReaderParameters parameters) 19 | { 20 | var extensions = name.IsWindowsRuntime ? new[] { ".winmd", ".dll" } : new[] { ".exe", ".dll" }; 21 | 22 | foreach (var extension in extensions) 23 | { 24 | string file = _references.FirstOrDefault(r => Path.GetFileName(r) == name.Name + extension); 25 | if (file == null || !File.Exists(file)) 26 | continue; 27 | try 28 | { 29 | return GetAssembly(file, parameters); 30 | } 31 | catch (System.BadImageFormatException) 32 | { 33 | //skip assemblies we cannot load 34 | } 35 | } 36 | 37 | return base.LookupAssembly(name, parameters); 38 | } 39 | 40 | private AssemblyDefinition GetAssembly(string file, ReaderParameters parameters) 41 | { 42 | if (parameters.AssemblyResolver == null) 43 | parameters.AssemblyResolver = this; 44 | 45 | return ModuleDefinition.ReadModule(file, parameters).Assembly; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Analyzer.Tests/Analyzers/CorrectDefinitionsTests.cs: -------------------------------------------------------------------------------- 1 | using TestHelper; 2 | using Xunit; 3 | 4 | namespace AspectInjector.Analyzer.Test.Analyzers 5 | { 6 | public abstract class CorrectDefinitionsTests : CodeFixVerifier 7 | { 8 | [Fact] 9 | public void No_Code_Doesnt_Throw_Analysis_Error() 10 | { 11 | var test = @""; 12 | VerifyCSharpDiagnostic(test); 13 | } 14 | 15 | [Fact] 16 | public void All_Valid_Doesnt_Throw_Analysis_Error() 17 | { 18 | var test = 19 | @"using AspectInjector.Broker; 20 | using System; 21 | namespace TestNameSpace 22 | { 23 | [Mixin(typeof(ITestInterface))] 24 | [Aspect(Scope.Global, Factory = typeof(FakeFactory))] 25 | class TypeClass : ITestInterface 26 | { 27 | public TypeClass(string value){} 28 | 29 | [Advice(Kind.Before)] 30 | public void Before( 31 | [Argument(Source.Instance)] object i, 32 | [Argument(Source.Type)] Type t, 33 | [Argument(Source.Method)] System.Reflection.MethodBase m, 34 | [Argument(Source.Name)] string n, 35 | [Argument(Source.Arguments)] object[] a, 36 | [Argument(Source.ReturnType)] Type rt, 37 | [Argument(Source.Injections)] Attribute[] inj 38 | ){} 39 | 40 | [Advice(Kind.After)] 41 | public void After( 42 | [Argument(Source.Instance)] object i, 43 | [Argument(Source.Type)] Type t, 44 | [Argument(Source.Method)] System.Reflection.MethodBase m, 45 | [Argument(Source.Name)] string n, 46 | [Argument(Source.Arguments)] object[] a, 47 | [Argument(Source.ReturnType)] Type rt, 48 | [Argument(Source.ReturnValue)] object rv, 49 | [Argument(Source.Injections)] Attribute[] inj 50 | ){} 51 | 52 | [Advice(Kind.Around)] 53 | public object Around( 54 | [Argument(Source.Instance)] object i, 55 | [Argument(Source.Type)] Type t, 56 | [Argument(Source.Method)] System.Reflection.MethodBase m, 57 | [Argument(Source.Name)] string n, 58 | [Argument(Source.Arguments)] object[] a, 59 | [Argument(Source.ReturnType)] Type rt, 60 | [Argument(Source.Target)] Func target, 61 | [Argument(Source.Injections)] Attribute[] inj 62 | ){ return null; } 63 | 64 | } 65 | 66 | interface ITestInterface {} 67 | 68 | class FakeFactory { 69 | public static object GetInstance(Type type){ 70 | return null; 71 | } 72 | } 73 | }"; 74 | VerifyCSharpDiagnostic(test); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/AspectInjector.Analyzer.Tests/AspectInjector.Analyzer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Generics/AspectInjector.Tests.Generics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Debug;Release;DebugTests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Generics/Class2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspectInjector.GenericTests 8 | { 9 | class Class2 10 | { 11 | public TestClass c1; 12 | 13 | public Class2() 14 | { 15 | c1 = new TestClass(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Integrity/AspectInjector.Tests.Integrity.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Integrity/PDBTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspectInjector.Tests.Integrity 8 | { 9 | internal class PDBTest 10 | { 11 | 12 | public void TestPDB() 13 | { 14 | // TODO :: 15 | } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/AccessModifiersTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Reflection; 4 | using Xunit; 5 | 6 | namespace AspectInjector.Tests.Runtime.Advices 7 | { 8 | public class AccessModifiersTests 9 | { 10 | private TestTarget _testClass = new TestTarget(); 11 | 12 | [Fact] 13 | public void Advices_Inject_Into_Internal_Static() 14 | { 15 | TestTarget.InternalStatic(); 16 | } 17 | 18 | [Fact] 19 | public void Advices_Inject_Into_Internal() 20 | { 21 | _testClass.Internal(); 22 | } 23 | 24 | [Fact] 25 | public void Advices_Inject_Skips_Public() 26 | { 27 | _testClass.Public(); 28 | } 29 | 30 | [AccessTestAspect] 31 | internal class TestTarget 32 | { 33 | public void Public() { } 34 | internal void Internal() => Assert.True(false); 35 | internal static void InternalStatic() => Assert.True(false); 36 | } 37 | 38 | [Aspect(Scope.Global)] 39 | [Injection(typeof(AccessTestAspect))] 40 | public class AccessTestAspect : Attribute 41 | { 42 | [Advice(Kind.Around, Targets = Target.Internal)] 43 | public object TestAccess([Argument(Source.Metadata)] MethodBase method) 44 | { 45 | Assert.True(method.IsAssembly); 46 | Assert.False(method.IsPublic); 47 | return null; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/CompilerGeneratedTargetsTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Advices 8 | { 9 | 10 | 11 | public class CompilerGeneratedTargetsTests 12 | { 13 | [TestAspect] 14 | public class TestTarget 15 | { 16 | public void Method() 17 | { 18 | int[] a = { 0, 1 }; 19 | a.Single(x => x == 1); 20 | } 21 | 22 | public async Task AsyncMethod() 23 | { 24 | await Task.Delay(1); 25 | } 26 | } 27 | 28 | [Aspect(Scope.Global)] 29 | [Injection(typeof(TestAspect))] 30 | public class TestAspect : Attribute 31 | { 32 | public static int beforeCalls = 0; 33 | public static int afterCalls = 0; 34 | public static int aroundCalls = 0; 35 | 36 | [Advice(Kind.Before)] 37 | public void Before() 38 | { 39 | beforeCalls++; 40 | } 41 | 42 | [Advice(Kind.After)] 43 | public void After() 44 | { 45 | afterCalls++; 46 | } 47 | 48 | [Advice(Kind.Around)] 49 | public object Around([Argument(Source.Target)] Func target, [Argument(Source.Arguments)] object[] args) 50 | { 51 | aroundCalls++; 52 | return target(args); 53 | } 54 | 55 | public static void Reset() 56 | { 57 | beforeCalls = 0; 58 | afterCalls = 0; 59 | aroundCalls = 0; 60 | } 61 | } 62 | 63 | 64 | [Fact] 65 | public void Does_Not_Inject_Into_Anonymous_Methods() 66 | { 67 | var target = new TestTarget(); 68 | TestAspect.Reset(); 69 | target.Method(); 70 | Assert.Equal(3, TestAspect.beforeCalls + TestAspect.afterCalls + TestAspect.aroundCalls); 71 | } 72 | 73 | [Fact] 74 | public void Does_Not_Inject_Into_Anonymous_AsyncStateMashines() 75 | { 76 | var target = new TestTarget(); 77 | TestAspect.Reset(); 78 | target.AsyncMethod().GetAwaiter().GetResult(); 79 | Assert.Equal(3, TestAspect.beforeCalls + TestAspect.afterCalls + TestAspect.aroundCalls); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/DebugTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Advices 8 | { 9 | public class DebugTests 10 | { 11 | [Fact(/*Skip = "Only debug"*/)] 12 | public void DebugWorks() 13 | { 14 | new TestClass().Do(); 15 | } 16 | 17 | [Aspect(Scope.Global)] 18 | [Injection(typeof(TestAspect))] 19 | public class TestAspect : Attribute 20 | { 21 | [Advice(Kind.Around)] 22 | public object Around([Argument(Source.Arguments)] object[] args, [Argument(Source.Target)] Func target) 23 | { 24 | return target(args); 25 | } 26 | } 27 | 28 | public class TestClass 29 | { 30 | [TestAspect] 31 | public void Do() 32 | { 33 | System.Diagnostics.Trace.WriteLine("Test"); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/FilterTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using Xunit; 5 | 6 | namespace AspectInjector.Tests.Advices 7 | { 8 | [SkipUnmanagedAndAbstractTests_Aspect] 9 | public abstract class SkipAbstractTests 10 | { 11 | public abstract int MessageBox(); 12 | } 13 | 14 | [SkipUnmanagedAndAbstractTests_Aspect] 15 | public class SkipUnmanagedTests 16 | { 17 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 18 | public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options); 19 | } 20 | 21 | [Aspect(Scope.Global)] 22 | [Injection(typeof(SkipUnmanagedAndAbstractTests_Aspect))] 23 | public class SkipUnmanagedAndAbstractTests_Aspect :Attribute 24 | { 25 | [Advice(Kind.Around)] 26 | public object Trace() 27 | { 28 | return 0; 29 | } 30 | } 31 | 32 | public class FilterTests 33 | { 34 | [Fact] 35 | public void Advices_InjectAfterMethod_NameFilter() 36 | { 37 | Checker.Passed = false; 38 | 39 | var a = new FilterTests_Target(); 40 | a.Do123(); 41 | 42 | Assert.True(Checker.Passed); 43 | } 44 | 45 | [FilterTests_Aspect] 46 | public class FilterTests_Target 47 | { 48 | [FilterTests_Aspect] 49 | public void Do123() 50 | { 51 | } 52 | } 53 | 54 | [Aspect(Scope.Global)] 55 | [Injection(typeof(FilterTests_Aspect))] 56 | public class FilterTests_Aspect : Attribute 57 | { 58 | public int Counter = 0; 59 | 60 | [Advice(Kind.After, Targets = Target.Method)] 61 | public void AfterMethod() 62 | { 63 | Counter++; 64 | Checker.Passed = Counter == 1; 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/NestedClassesTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Advices 6 | { 7 | 8 | public class NestedClassesTests 9 | { 10 | [Fact] 11 | public void Advices_InjectBeforeMethod_NestedClass() 12 | { 13 | Checker.Passed = false; 14 | var testClass = new NestedClassesTests_Target(); 15 | testClass.Do(); 16 | Assert.True(Checker.Passed); 17 | } 18 | 19 | [NestedClassesTests_Aspect] 20 | private class NestedClassesTests_Target 21 | { 22 | public void Do() 23 | { 24 | } 25 | } 26 | } 27 | 28 | [Aspect(Scope.Global)] 29 | [Injection(typeof(NestedClassesTests_Aspect))] 30 | public class NestedClassesTests_Aspect:Attribute 31 | { 32 | [Advice(Kind.Before, Targets = Target.Method)] 33 | public void Fact() 34 | { 35 | Checker.Passed = true; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Advices/OrderTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Runtime.Advices 6 | { 7 | public class OrderTests 8 | { 9 | private OrderTests_Target _beforeTestClass = new OrderTests_Target(); 10 | 11 | [Fact] 12 | public void Advices_InjectBeforeMethod_Ordered() 13 | { 14 | Checker.Passed = false; 15 | _beforeTestClass.Fact(); 16 | Assert.True(Checker.Passed); 17 | } 18 | 19 | 20 | internal class OrderTests_Target 21 | { 22 | [Trigger] 23 | public void Fact() 24 | { 25 | } 26 | } 27 | 28 | [Injection(typeof(OrderTests_Aspect1), Priority = 0)] 29 | [Injection(typeof(OrderTests_Aspect3), Priority = 2)] 30 | [Injection(typeof(OrderTests_Aspect2), Priority = 1)] 31 | class Trigger : Attribute { } 32 | 33 | [Aspect(Scope.Global)] 34 | public class OrderTests_Aspect1 35 | { 36 | [Advice(Kind.Before)] 37 | public void BeforeMethod() 38 | { 39 | Checker.Passed = false; 40 | } 41 | } 42 | 43 | [Aspect(Scope.Global)] 44 | public class OrderTests_Aspect2 45 | { 46 | [Advice(Kind.Before)] 47 | public void BeforeMethod() 48 | { 49 | Checker.Passed = false; 50 | } 51 | } 52 | 53 | [Aspect(Scope.Global)] 54 | public class OrderTests_Aspect3 55 | { 56 | [Advice(Kind.Before)] 57 | public void BeforeMethod() 58 | { 59 | Checker.Passed = true; 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/After/TargetsIstanceTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Tests.Assets; 2 | 3 | namespace AspectInjector.Tests.Runtime.After 4 | { 5 | public class Instance_Tests : Global_Tests 6 | { 7 | protected override string Token => InstanceAspect.AfterExecuted; 8 | } 9 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Around/TargetsIstanceTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Tests.Assets; 2 | 3 | namespace AspectInjector.Tests.Runtime.Around 4 | { 5 | public class Instance_Tests : Global_Tests 6 | { 7 | protected override string EnterToken => InstanceAspect.AroundEnter; 8 | protected override string ExitToken => InstanceAspect.AroundExit; 9 | } 10 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/AspectInjector.Tests.Runtime.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net8.0;net462;net481 4 | Debug;Release;DebugTests 5 | 6 | 7 | 8 | 9 | all 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Before/TargetsIstanceTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Tests.Assets; 2 | 3 | namespace AspectInjector.Tests.Runtime.Before 4 | { 5 | public class Instance_Tests : Global_Tests 6 | { 7 | protected override string Token => InstanceAspect.BeforeExecuted; 8 | } 9 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Config.cs: -------------------------------------------------------------------------------- 1 | [assembly: Xunit.CollectionBehavior(Xunit.CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] 2 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Control.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Tests.Assets; 2 | using System; 3 | 4 | namespace AspectInjector.Tests.Runtime 5 | { 6 | class ControlWrapper 7 | { 8 | class Control : IAssetIface1Wrapper.IAssetIface1, IAssetIface1Wrapper.IAssetIface1 9 | { 10 | Tuple IAssetIface1Wrapper.IAssetIface1.TestProperty { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 11 | Tuple IAssetIface1Wrapper.IAssetIface1.TestProperty { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 12 | 13 | event EventHandler> IAssetIface1Wrapper.IAssetIface1.TestEvent 14 | { 15 | add 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | remove 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | 26 | event EventHandler> IAssetIface1Wrapper.IAssetIface1.TestEvent 27 | { 28 | add 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | remove 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | 39 | void IAssetIface1Wrapper.IAssetIface1.EmptyMethod() 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | void IAssetIface1Wrapper.IAssetIface1.EmptyMethod() 45 | { 46 | throw new NotImplementedException(); 47 | } 48 | 49 | Tuple IAssetIface1Wrapper.IAssetIface1.TestMethod(int a1, Asset1 a2, Asset2 a3, Asset1 a4, T3 a5, ref int ar1, ref Asset1 ar2, ref Asset2 ar3, ref Asset1 ar4, ref T3 ar5, out int ao1, out Asset1 ao2, out Asset2 ao3, out Asset1 ao4, out T3 ao5) 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | Tuple IAssetIface1Wrapper.IAssetIface1.TestMethod(int a1, Asset1 a2, Asset1 a3, Asset2 a4, T3 a5, ref int ar1, ref Asset1 ar2, ref Asset1 ar3, ref Asset2 ar4, ref T3 ar5, out int ao1, out Asset1 ao2, out Asset1 ao3, out Asset2 ao4, out T3 ao5) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/General/AspectFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using Xunit; 3 | using System; 4 | 5 | namespace AspectInjector.Tests.General 6 | { 7 | 8 | public class AspectFactoryTests 9 | { 10 | [Fact] 11 | public void General_AspectFactory_CreateAspect() 12 | { 13 | Checker.Passed = false; 14 | var test = new AspectFactoryTests_Target(); 15 | Assert.True(Checker.Passed); 16 | } 17 | } 18 | 19 | [AspectFactoryTests_Aspect] 20 | public class AspectFactoryTests_Target 21 | { 22 | } 23 | 24 | [Aspect(Scope.PerInstance, Factory = typeof(AspectFactory))] 25 | [Injection(typeof(AspectFactoryTests_Aspect))] 26 | public class AspectFactoryTests_Aspect : Attribute 27 | { 28 | private static object aaa; 29 | 30 | [Advice(Kind.After, Targets = Target.Constructor)] 31 | public void Fact() 32 | { 33 | } 34 | 35 | private static void ololo() 36 | { 37 | aaa = AspectFactory.GetInstance(typeof(AspectFactoryTests_Aspect)); 38 | } 39 | } 40 | 41 | public class AspectFactory 42 | { 43 | public static object GetInstance(Type aspectType) 44 | { 45 | Checker.Passed = true; 46 | return Activator.CreateInstance(aspectType); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/General/AspectScopeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using AspectInjector.Broker; 4 | 5 | namespace AspectInjector.Tests.General 6 | { 7 | 8 | public class AspectScopeTests 9 | { 10 | [Fact] 11 | public void SCOPE_Create_Aspect_Per_Instance() 12 | { 13 | AspectScopeTests_PerInstanceAspect._counter = 0; 14 | for (int i = 0; i < 10; i++) 15 | { 16 | var t = new AspectScopeTests_Target(); 17 | Assert.Equal(i + 1, AspectScopeTests_PerInstanceAspect._counter); 18 | } 19 | } 20 | 21 | [Fact] 22 | public void SCOPE_Create_Global_Aspect() 23 | { 24 | for (int i = 0; i < 10; i++) 25 | { 26 | var t = new AspectScopeTests_Target(); 27 | Assert.Equal(1, AspectScopeTests_GlobalAspect._counter); 28 | } 29 | } 30 | } 31 | 32 | [Aspect(Scope.PerInstance)] 33 | [Injection(typeof(AspectScopeTests_PerInstanceAspect))] 34 | public class AspectScopeTests_PerInstanceAspect: Attribute 35 | { 36 | public static int _counter; 37 | 38 | public AspectScopeTests_PerInstanceAspect() 39 | { 40 | _counter++; 41 | } 42 | 43 | [Advice(Kind.Before, Targets = Target.Method)] 44 | public void Do() 45 | { 46 | } 47 | } 48 | 49 | [Aspect(Scope.Global)] 50 | [Injection(typeof(AspectScopeTests_GlobalAspect))] 51 | public class AspectScopeTests_GlobalAspect: Attribute 52 | { 53 | public static int _counter; 54 | 55 | public AspectScopeTests_GlobalAspect() 56 | { 57 | _counter++; 58 | } 59 | 60 | [Advice(Kind.Before, Targets = Target.Method)] 61 | public void Do() 62 | { 63 | } 64 | } 65 | 66 | [AspectScopeTests_PerInstanceAspect] 67 | [AspectScopeTests_GlobalAspect] 68 | internal class AspectScopeTests_Target 69 | { 70 | public void Fact() 71 | { 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/General/PropagationControlTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Runtime.General 6 | { 7 | public class PropagationControlTests 8 | { 9 | private readonly TestClass _testTarget; 10 | 11 | public PropagationControlTests() 12 | { 13 | _testTarget = new TestClass(); 14 | } 15 | 16 | [Fact] 17 | public void Propagation_Options_Correctly_Processed() 18 | { 19 | _testTarget.Method(); 20 | _testTarget.Event += (s, e) => { }; 21 | _testTarget.Ev2ent += (s, e) => { }; 22 | _testTarget.Property = ""; 23 | } 24 | 25 | [EventTrigger] 26 | [PropertyTrigger] 27 | [MethodTrigger] 28 | public class TestClass 29 | { 30 | public event EventHandler Event; 31 | public event EventHandler Ev2ent; 32 | public string Property { get; set; } 33 | public void Method() { } 34 | } 35 | 36 | [Injection(typeof(SuccessAspect), Propagation = PropagateTo.Events, PropagationFilter = "Event")] 37 | public class EventTrigger : Attribute { } 38 | 39 | [Injection(typeof(SuccessAspect), Propagation = PropagateTo.Properties)] 40 | public class PropertyTrigger : Attribute { } 41 | 42 | [Injection(typeof(SuccessAspect), Propagation = PropagateTo.Methods)] 43 | public class MethodTrigger : Attribute { } 44 | 45 | 46 | [Aspect(Scope.Global)] 47 | public class SuccessAspect 48 | { 49 | [Advice(Kind.Before, Targets =Target.Method | Target.EventAdd | Target.EventRemove | Target.Getter | Target.Setter )] 50 | public void Success([Argument(Source.Name)] string name, [Argument(Source.Triggers)] Attribute[] triggers) 51 | { 52 | switch (name) 53 | { 54 | case nameof(TestClass.Event): 55 | Assert.True(triggers.Length == 1 && triggers[0] is EventTrigger);break; 56 | case nameof(TestClass.Property): 57 | Assert.True(triggers.Length == 1 && triggers[0] is PropertyTrigger); break; 58 | case nameof(TestClass.Method): 59 | Assert.True(triggers.Length == 1 && triggers[0] is MethodTrigger); break; 60 | default: 61 | Assert.True(false);break; 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/General/ReferencesTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Tests.Assets; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace AspectInjector.Tests.Runtime.General 9 | { 10 | public class ReferencesTest 11 | { 12 | [Fact] 13 | public void NoRef_Runtime_To_PrivateCoreLib() 14 | { 15 | var refs = typeof(ReferencesTest).Assembly.GetReferencedAssemblies(); 16 | Assert.DoesNotContain(refs, r => r.Name == "System.Private.CoreLib"); 17 | } 18 | 19 | [Fact] 20 | public void NoRef_RuntimeAssets_To_PrivateCoreLib() 21 | { 22 | var refs = typeof(TestLog).Assembly.GetReferencedAssemblies(); 23 | Assert.DoesNotContain(refs, r => r.Name == "System.Private.CoreLib"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/General/UnmanagedTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace AspectInjector.Tests.General 6 | { 7 | //The compilation is just failed if injector tries to process external methods 8 | [UnmanagedTests_Aspect] 9 | public class UnmanagedTests 10 | { 11 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 12 | public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options); 13 | } 14 | 15 | [Aspect(Scope.Global)] 16 | [Injection(typeof(UnmanagedTests_Aspect))] 17 | public class UnmanagedTests_Aspect : Attribute 18 | { 19 | [Advice(Kind.After, Targets = Target.Method)] 20 | public void Trace() 21 | { 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/InheritedInjections/InheritedInjectionsTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers; 3 | using System; 4 | using Xunit; 5 | 6 | namespace AspectInjector.Tests.Runtime.InheritedInjections 7 | { 8 | [Aspect(Scope.Global)] 9 | public class TestAspect 10 | { 11 | [Advice(Kind.Around)] 12 | public object Around( 13 | [Argument(Source.Target)] Func target, 14 | [Argument(Source.Arguments)] object[] args, 15 | [Argument(Source.Triggers)] Attribute[] attrs) 16 | { 17 | var res = target(args); 18 | Checker.Passed = true; 19 | return res; 20 | } 21 | } 22 | 23 | [Injection(typeof(TestAspect), Inherited = true)] 24 | public abstract class BaseLocalAttribute : Attribute { 25 | public int Value { get; set; } 26 | public int Value2; 27 | } 28 | 29 | public class RealAttribute : BaseLocalAttribute { } 30 | 31 | public class RemoteAttribute : BaseAttribute { } 32 | 33 | internal class TestClass 34 | { 35 | [Real(Value = 1, Value2 = 2)] 36 | [Remote] 37 | public void Do() { } 38 | } 39 | 40 | public class InheritedInjectionsTests 41 | { 42 | [Fact] 43 | public void InheritedInjection() 44 | { 45 | Checker.Passed = false; 46 | new TestClass().Do(); 47 | 48 | Assert.True(Checker.Passed); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Injections/DirectInjectionsTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Runtime.Injections 6 | { 7 | public class DirectInjectionsTests 8 | { 9 | [Fact] 10 | public void CanInjectIntoGetterDirectly() 11 | { 12 | Checker.Passed = false; 13 | var t = new TestTarget(); 14 | var a = t.Text; 15 | Assert.True(Checker.Passed); 16 | Checker.Passed = false; 17 | } 18 | 19 | private class TestTarget 20 | { 21 | public string Text { [TestAspect] get; set; } 22 | } 23 | 24 | [Aspect(Scope.Global)] 25 | [Injection(typeof(TestAspect))] 26 | public class TestAspect : Attribute 27 | { 28 | private int _count = 0; 29 | 30 | [Advice(Kind.Before)] 31 | public void Before() 32 | { 33 | _count++; 34 | Assert.Equal(1, _count); 35 | Checker.Passed = true; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Injections/InjectionOrderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using AspectInjector.Broker; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Injections 8 | { 9 | public class InjectionOrderTests 10 | { 11 | [Fact] 12 | public void AspectInstance_InHierarchy_MustBeOne() 13 | { 14 | var target = new TestInjectionTarget(); 15 | Assert.Equal(1, target.Test1()); 16 | Assert.Equal(2, target.Test2()); 17 | } 18 | 19 | [Aspect(Scope.PerInstance)] 20 | [Injection(typeof(TestAspect))] 21 | public class TestAspect : Attribute 22 | { 23 | public int Count { get; private set; } 24 | 25 | [Advice(Kind.Around, Targets = Target.Method)] 26 | public object Test( 27 | [Argument(Source.Arguments)] object[] arguments, 28 | [Argument(Source.Target)] Func target) 29 | { 30 | Count++; 31 | target(arguments); 32 | return Count; 33 | } 34 | } 35 | 36 | [TestAspect] 37 | private class TestInjectionTarget : TestInjectionTargetBase 38 | { 39 | public int Test2() 40 | { 41 | return 0; 42 | } 43 | } 44 | 45 | [TestAspect] 46 | private class TestInjectionTargetBase 47 | { 48 | public int Test1() 49 | { 50 | return 0; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Injections/InterfaceTriggers.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Injections 8 | { 9 | public class InterfaceTriggers 10 | { 11 | [Fact] 12 | public void Interface_Triggers_Work() 13 | { 14 | Checker.Passed = false; 15 | new TestTarget().Do(); 16 | Assert.True(Checker.Passed); 17 | } 18 | 19 | [Aspect(Scope.Global)] 20 | public class TestAspect 21 | { 22 | [Advice(Kind.Before, Targets = Target.Method)] 23 | public void Before() 24 | { 25 | Checker.Passed = true; 26 | } 27 | } 28 | 29 | [Injection(typeof(TestAspect))] 30 | public interface INeedInjection 31 | { 32 | 33 | } 34 | 35 | public class TestTarget : INeedInjection 36 | { 37 | public void Do() 38 | { 39 | 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Injections/SkipInjectionsTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Runtime.Injections 6 | { 7 | [TestSelfInjection] 8 | public class SkipInjectionsTests 9 | { 10 | [Fact] 11 | public void DontInjectIntoInjection() 12 | { 13 | var t = new TestTarget(); 14 | t.Test(); 15 | var i1 = new TestSelfInjection(); 16 | i1.Test(); 17 | var i2 = new TestOtherInjection(); 18 | i2.Test(); 19 | Assert.Equal(2, TestTarget.Counter); 20 | } 21 | 22 | private class TestTarget 23 | { 24 | public static int Counter = 0; 25 | 26 | public void Test() { } 27 | } 28 | 29 | [Injection(typeof(TestAspect))] 30 | private class TestSelfInjection : Attribute 31 | { 32 | public void Test() { } 33 | } 34 | 35 | [Injection(typeof(TestAspect))] 36 | private class TestOtherInjection : Attribute 37 | { 38 | public void Test() { } 39 | } 40 | 41 | [Aspect(Scope.Global)] 42 | public class TestAspect 43 | { 44 | [Advice(Kind.Before, Targets = Target.Method)] 45 | public void Before() 46 | { 47 | TestTarget.Counter++; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Interfaces/InheritanceTests.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Interfaces 6 | { 7 | 8 | public class InheritanceTests 9 | { 10 | [Fact] 11 | public void Interfaces_InjectionSupportsInheritance() 12 | { 13 | var ti = (IInheritanceTests)new InheritanceTests_Target(); 14 | var r1 = ti.GetAspectType(); 15 | 16 | var tib = (IInheritanceTests)new InheritanceTests_Base(); 17 | var r2 = tib.GetAspectType(); 18 | 19 | Assert.Equal(r1, r2); 20 | } 21 | 22 | [InheritanceTests_Aspect] 23 | public class InheritanceTests_Base { } 24 | 25 | [InheritanceTests_Aspect] 26 | public class InheritanceTests_Target : InheritanceTests_Base { } 27 | 28 | public interface IInheritanceTests 29 | { 30 | string GetAspectType(); 31 | 32 | int GetAspectHash(); 33 | } 34 | 35 | [Mixin(typeof(IInheritanceTests))] 36 | [Aspect(Scope.Global)] 37 | [Injection(typeof(InheritanceTests_Aspect))] 38 | public class InheritanceTests_Aspect : Attribute, IInheritanceTests 39 | { 40 | public string GetAspectType() 41 | { 42 | return GetType().ToString(); 43 | } 44 | 45 | public int GetAspectHash() 46 | { 47 | return GetHashCode(); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Issues/0097.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Tests.Assets; 3 | using AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers; 4 | using System; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Issues 8 | { 9 | public class Issue_0097 10 | { 11 | [Fact] 12 | public void Fixed() 13 | { 14 | new Target().Value = 12; 15 | } 16 | 17 | private class SuperTargetBase : TestBaseClass 18 | { 19 | [TestAspect] 20 | public int Number { get; set; } 21 | } 22 | 23 | private class TargetBase : SuperTargetBase 24 | { 25 | [TestAspect] 26 | public string Text { get; set; } 27 | } 28 | 29 | private class Target : TargetBase 30 | { 31 | [TestAspect] 32 | public double Value { get; set; } 33 | 34 | void Control() 35 | { 36 | var a = base.Text; 37 | var b = base.Number; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Issues/0098.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using Xunit; 4 | 5 | namespace AspectInjector.Tests.Runtime.Issues 6 | { 7 | public class Issue_0098 8 | { 9 | [Fact] 10 | public void Fixed() 11 | { 12 | new Target().Variable = new object(); 13 | } 14 | 15 | [Aspect(Scope.PerInstance)] 16 | [Injection(typeof(TestAspect))] 17 | public class TestAspect : Attribute 18 | { 19 | [Advice(Kind.Before)] 20 | public void Before() 21 | { 22 | Console.WriteLine("Before"); 23 | } 24 | 25 | [Advice(Kind.After)] 26 | public void After() 27 | { 28 | Console.WriteLine("After"); 29 | } 30 | } 31 | 32 | private class Target 33 | { 34 | 35 | private object _variable = null; 36 | 37 | [TestAspect] 38 | public object Variable 39 | { 40 | get 41 | { 42 | 43 | try { return _variable; } 44 | catch (Exception) { throw; } 45 | } 46 | set 47 | { 48 | var oldValue = _variable; 49 | var newValue = value; 50 | 51 | try 52 | { 53 | _variable = value; 54 | 55 | if (oldValue != default && oldValue != newValue) 56 | { 57 | LocalMethod(this, oldValue); 58 | } 59 | } 60 | catch (Exception) 61 | { 62 | _variable = oldValue; 63 | 64 | throw; 65 | } 66 | } 67 | } 68 | 69 | private void LocalMethod(object o1, object o2) { } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Issues/0114.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace AspectInjector.Tests.Runtime.Issues 8 | { 9 | public class Issue_0114 10 | { 11 | [Fact] 12 | public void Test() 13 | { 14 | new TestClass().Do(); 15 | } 16 | 17 | class TestClass 18 | { 19 | [TestInjection] 20 | public void Do() { 21 | Assert.True(false); 22 | } 23 | } 24 | 25 | [Aspect(Scope.Global)] 26 | public class TestAspect 27 | { 28 | [Advice(Kind.Around)] 29 | public object TestAdvice([Argument(Source.Triggers)] Attribute[] attributes) 30 | { 31 | Assert.NotEmpty(attributes); 32 | return null; 33 | } 34 | } 35 | 36 | [Injection(typeof(TestAspect))] 37 | public class TestInjection : Attribute 38 | { 39 | public TestInjection(string testArg = null) 40 | { 41 | 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Issues/0123.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Runtime.Serialization; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace AspectInjector.Tests.Runtime.Issues 12 | { 13 | 14 | public class Issue_0123 15 | { 16 | [Fact] 17 | public void Fixed() 18 | { 19 | Checker.Passed = false; 20 | new HomeController().ActionOnlyAdminsCanDo().Wait(); 21 | Assert.True(Checker.Passed); 22 | } 23 | 24 | public enum UserRole 25 | { 26 | Guest, 27 | Normal, 28 | Admin, 29 | } 30 | 31 | public enum UserRole2 : byte 32 | { 33 | Guest, 34 | Normal, 35 | Admin, 36 | } 37 | 38 | public enum UserRole3 : long 39 | { 40 | Guest = long.MaxValue, 41 | Normal = 1, 42 | Admin = 2, 43 | } 44 | 45 | public class HomeController 46 | { 47 | [CheckPrivileges(new UserRole[] { UserRole.Admin }, new UserRole2[] { UserRole2.Admin }, new UserRole3[] { UserRole3.Guest })] 48 | public async Task ActionOnlyAdminsCanDo() 49 | { 50 | await Task.Delay(100); 51 | } 52 | } 53 | 54 | [Injection(typeof(CheckPrivilegesAspect))] 55 | [AttributeUsage(AttributeTargets.Method)] 56 | public sealed class CheckPrivileges : Attribute 57 | { 58 | public UserRole[] Roles { get; } 59 | 60 | public CheckPrivileges(UserRole[] roles, UserRole2[] roles2, UserRole3[] role3s) 61 | { 62 | Roles = roles; 63 | } 64 | } 65 | 66 | [Aspect(Scope.PerInstance)] 67 | public class CheckPrivilegesAspect 68 | { 69 | [Advice(Kind.Before)] 70 | public void Before([Argument(Source.Triggers)] Attribute[] attributes) 71 | { 72 | if (attributes[0] is CheckPrivileges cp && cp.Roles.Contains(UserRole.Admin)) 73 | Checker.Passed = true; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Issues/0140.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | 4 | namespace AspectInjector.Tests.Runtime.Issues 5 | { 6 | public class Issue_0140 7 | { 8 | [Aspect(Scope.PerInstance)] 9 | [Injection(typeof(TestAspect))] 10 | public class TestAspect : Attribute 11 | { 12 | [Advice(Kind.Before)] 13 | public void Before() 14 | { 15 | 16 | } 17 | } 18 | 19 | [Aspect(Scope.PerInstance)] 20 | [Injection(typeof(TestAspect))] 21 | public class TestAspect2 : Attribute 22 | { 23 | [Advice(Kind.Before)] 24 | public void Before() 25 | { 26 | 27 | } 28 | } 29 | 30 | [TestAspect] 31 | [TestAspect2] 32 | internal class ArgumentsTests_GenericClassConstructorChainTargetImpl : ArgumentsTests_GenericClassConstructorChainTarget 33 | { 34 | } 35 | 36 | [TestAspect] 37 | internal abstract class ArgumentsTests_GenericClassConstructorChainTarget where T : class 38 | { 39 | public ArgumentsTests_GenericClassConstructorChainTarget() 40 | { 41 | } 42 | 43 | public ArgumentsTests_GenericClassConstructorChainTarget(int a) : this() 44 | { 45 | } 46 | 47 | public ArgumentsTests_GenericClassConstructorChainTarget(int a, int b) : this(a) 48 | { 49 | } 50 | 51 | public void Fact() 52 | { 53 | } 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Mixins/TestClass.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using AspectInjector.Tests.Assets; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AspectInjector.Tests.Runtime.Mixins 10 | { 11 | internal class TestClassWrapper 12 | { 13 | [InjectInstanceAspect] 14 | [InjectGlobalAspect] 15 | private class TestClass 16 | { 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/Utils/Checker.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace AspectInjector.Tests 3 | { 4 | public static class Checker 5 | { 6 | public static bool Passed = false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.Runtime/test.key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pamidur/aspect-injector/2a4a154857f4389a96ff4a0421e97ff91e2e060d/tests/AspectInjector.Tests.Runtime/test.key.snk -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/AspectInjector.Tests.RuntimeAssets.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug;Release;DebugTests 4 | netstandard2.0;net8.0;net6.0;net462;net481 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers; buildtransitive 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/CrossAssemblyHelpers/BaseAttribute.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | 4 | namespace AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers 5 | { 6 | [Injection(typeof(TestAspect),Inherited = true)] 7 | public class BaseAttribute : Attribute 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/CrossAssemblyHelpers/BaseGenericClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers 6 | { 7 | public class BaseGenericClass 8 | { 9 | public class NestedGenericClass : BaseGenericClass 10 | { 11 | public class NestedGeneric2Class : NestedGenericClass 12 | { 13 | public interface NestedGenericInterface 14 | { 15 | H GetH(G g, I i, U u, T t) 16 | where G : NestedGenericInterface 17 | ; 18 | 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/CrossAssemblyHelpers/SomeType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers 6 | { 7 | public class SomeType 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/CrossAssemblyHelpers/TestAspect.cs: -------------------------------------------------------------------------------- 1 | using AspectInjector.Broker; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers 7 | { 8 | [Aspect(Scope.PerInstance)] 9 | [Injection(typeof(TestAspect))] 10 | public class TestAspect : Attribute 11 | { 12 | [Advice(Kind.Before)] 13 | public void Before() 14 | { 15 | Console.WriteLine("Before"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/CrossAssemblyHelpers/TestBaseClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspectInjector.Tests.RuntimeAssets.CrossAssemblyHelpers 6 | { 7 | [TestAspect] 8 | public class TestBaseClass 9 | { 10 | public SomeType SomeField { get; set; } 11 | public T GenericField { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/TestAssets.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static AspectInjector.Tests.Assets.IAssetIface1Wrapper; 3 | 4 | namespace AspectInjector.Tests.Assets 5 | { 6 | public class TestAssets 7 | { 8 | public static int asset1 = 1; 9 | public static Asset1 asset2 = new Asset1(); 10 | public static short asset3 = 2; 11 | public static IAssetIface1 asset4 = new Asset2(); 12 | public static Asset2 asset5 = new Asset2(); 13 | } 14 | 15 | public class Asset1 16 | { 17 | } 18 | 19 | public class Asset2 : IAssetIface1 20 | { 21 | public Tuple TestProperty 22 | { 23 | get 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | set 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | } 33 | 34 | public event EventHandler> TestEvent; 35 | 36 | public void EmptyMethod() 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | public Tuple TestMethod(int a1, Asset1 a2, Asset1 a3, Asset1 a4, T3 a5, ref int ar1, ref Asset1 ar2, ref Asset1 ar3, ref Asset1 ar4, ref T3 ar5, out int ao1, out Asset1 ao2, out Asset1 ao3, out Asset1 ao4, out T3 ao5) 42 | { 43 | throw new NotImplementedException(); 44 | } 45 | } 46 | 47 | public class IAssetIface1Wrapper 48 | { 49 | public interface IAssetIface1 50 | { 51 | Tuple TestMethod( 52 | int a1, Asset1 a2, T1 a3, T2 a4, T3 a5, 53 | ref int ar1, ref Asset1 ar2, ref T1 ar3, ref T2 ar4, ref T3 ar5, 54 | out int ao1, out Asset1 ao2, out T1 ao3, out T2 ao4, out T3 ao5 55 | ); 56 | 57 | Tuple TestProperty { get; set; } 58 | 59 | event EventHandler> TestEvent; 60 | 61 | void EmptyMethod(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/TestLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspectInjector.Tests.Assets 8 | { 9 | public static class TestLog 10 | { 11 | private static readonly List _log = new List(); 12 | public static IReadOnlyList Log => _log; 13 | 14 | public static void Write(string @event) 15 | { 16 | _log.Add(@event); 17 | } 18 | 19 | public static void Reset() 20 | { 21 | var t = typeof(TestLog); 22 | _log.Clear(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.RuntimeAssets/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pamidur/aspect-injector/2a4a154857f4389a96ff4a0421e97ff91e2e060d/tests/AspectInjector.Tests.RuntimeAssets/key.snk -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.VBRuntime/AspectInjector.Tests.VBRuntime.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net8.0 4 | Debug;Release;DebugTests 5 | 6 | 7 | 8 | 9 | all 10 | all 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/AspectInjector.Tests.VBRuntime/Issue_0182.vb: -------------------------------------------------------------------------------- 1 | Imports AspectInjector.Broker 2 | Imports AspectInjector.Tests.Assets 3 | Imports Xunit 4 | 5 | Namespace AspectInjector.Tests.VBRuntime 6 | 7 | Public Class Issue_0182 8 | 9 | 10 | Public Async Sub AdviceBefore_Methods_Passes() 11 | Dim vb = New TestClassVbAspect() 12 | Dim result = Await vb.TestAsyncMethod("Test") 13 | Assert.Equal("Test", result) 14 | End Sub 15 | 16 | Partial Friend NotInheritable Class TestClassVbAspect 17 | 18 | Public Async Function TestAsyncMethod(Of T)(ByVal obj As T) As Task(Of T) 19 | Return Await Task.FromResult(obj) 20 | End Function 21 | End Class 22 | 23 | 24 | 25 | Public Class InjectInstanceAspect 26 | Inherits Attribute 27 | End Class 28 | 29 | 30 | Public Class InstanceAspect 31 | Inherits TestAspectBase 32 | 33 | 34 | Public Sub AspectMethod() 35 | End Sub 36 | 37 | End Class 38 | 39 | End Class 40 | 41 | End Namespace 42 | 43 | -------------------------------------------------------------------------------- /tests/Aspects.Tests/Aspects.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug;Release;DebugTests 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/Aspects.Tests/FreezableTests.cs: -------------------------------------------------------------------------------- 1 | using Aspects.Freezable; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Aspests.Tests 6 | { 7 | public class FreezableTests 8 | { 9 | [Freezable] 10 | class TestClass 11 | { 12 | public string Data { get; set; } 13 | } 14 | 15 | [Fact] 16 | public void Frozen_Object_Does_Not_Allow_Change_Property() 17 | { 18 | var target = new TestClass(); 19 | target.Data = "test1"; 20 | ((IFreezable)target).Freeze(); 21 | 22 | Assert.Throws(() => target.Data = "test2"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Aspects.Tests/NotifyTests.cs: -------------------------------------------------------------------------------- 1 | using Aspects.Notify; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using Xunit; 5 | 6 | namespace Aspests.Tests 7 | { 8 | public class NotifyTests 9 | { 10 | class TestClass 11 | { 12 | [Notify] 13 | [NotifyAlso(nameof(FullName))] 14 | public string FirstName { get; set; } 15 | 16 | [Notify] 17 | [NotifyAlso(nameof(FullName))] 18 | public string LastName { get; set; } 19 | 20 | public byte Age { get; set; } 21 | 22 | public string FullName => $"{FirstName} {LastName}"; 23 | 24 | } 25 | 26 | [Fact] 27 | public void Notify_Aspect_Should_Raise_Events() 28 | { 29 | var target = new TestClass(); 30 | 31 | var raised = new List(); 32 | 33 | (target as INotifyPropertyChanged).PropertyChanged += (s, e) => 34 | { 35 | raised.Add(e.PropertyName); 36 | }; 37 | 38 | target.FirstName = "Alex"; 39 | Assert.Collection(raised, 40 | s => Assert.Equal(nameof(target.FirstName), s), 41 | s => Assert.Equal(nameof(target.FullName), s) 42 | ); 43 | raised.Clear(); 44 | 45 | target.Age = 92; 46 | Assert.Empty(raised); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory)../src/AspectInjector/bin/$(Configuration)/netstandard2.0/AspectInjector.dll 5 | 6 | 7 | 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------