{"id":13592195,"url":"https://github.com/JSkimming/Castle.Core.AsyncInterceptor","last_synced_at":"2025-04-08T23:31:18.965Z","repository":{"id":37458235,"uuid":"60778941","full_name":"JSkimming/Castle.Core.AsyncInterceptor","owner":"JSkimming","description":"Library to simplify interception of asynchronous methods","archived":false,"fork":false,"pushed_at":"2023-12-12T07:18:25.000Z","size":534,"stargazers_count":296,"open_issues_count":24,"forks_count":43,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-11-01T16:42:55.445Z","etag":null,"topics":["async","asynchronous-methods","castle-core","dynamic-proxy","dynamicproxy","intercept-methods"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JSkimming.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-06-09T13:57:57.000Z","updated_at":"2024-10-14T15:57:58.000Z","dependencies_parsed_at":"2024-01-13T16:33:54.174Z","dependency_job_id":null,"html_url":"https://github.com/JSkimming/Castle.Core.AsyncInterceptor","commit_stats":{"total_commits":336,"total_committers":12,"mean_commits":28.0,"dds":0.5595238095238095,"last_synced_commit":"d714ddfa6e0b874a10fe42c09ffee276a82436ae"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JSkimming%2FCastle.Core.AsyncInterceptor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JSkimming%2FCastle.Core.AsyncInterceptor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JSkimming%2FCastle.Core.AsyncInterceptor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JSkimming%2FCastle.Core.AsyncInterceptor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JSkimming","download_url":"https://codeload.github.com/JSkimming/Castle.Core.AsyncInterceptor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223100258,"owners_count":17087388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","asynchronous-methods","castle-core","dynamic-proxy","dynamicproxy","intercept-methods"],"created_at":"2024-08-01T16:01:06.778Z","updated_at":"2024-11-06T13:30:31.960Z","avatar_url":"https://github.com/JSkimming.png","language":"C#","funding_links":[],"categories":["C#","C\\#"],"sub_categories":[],"readme":"# Castle.Core.AsyncInterceptor\n\n[![NuGet Version](https://img.shields.io/nuget/v/Castle.Core.AsyncInterceptor.svg)](https://www.nuget.org/packages/Castle.Core.AsyncInterceptor \"NuGet Version\")\n[![NuGet Downloads](https://img.shields.io/nuget/dt/Castle.Core.AsyncInterceptor.svg)](https://www.nuget.org/packages/Castle.Core.AsyncInterceptor \"NuGet Downloads\")\n[![Build status](https://img.shields.io/appveyor/ci/JSkimming/castle-core-asyncinterceptor/master.svg?label=AppVeyor)](https://ci.appveyor.com/project/JSkimming/castle-core-asyncinterceptor \"AppVeyor build status\")\n[![CircleCI build Status](https://img.shields.io/circleci/project/github/JSkimming/Castle.Core.AsyncInterceptor/master.svg?label=CircleCI)](https://circleci.com/gh/JSkimming/Castle.Core.AsyncInterceptor/tree/master \"CircleCI build Status\")\n[![codecov Coverage Status](https://img.shields.io/codecov/c/github/JSkimming/Castle.Core.AsyncInterceptor/master.svg?label=Codecov)](https://codecov.io/gh/JSkimming/Castle.Core.AsyncInterceptor \"Codecov Coverage Status\")\n[![Coveralls Coverage Status](https://img.shields.io/coveralls/github/JSkimming/Castle.Core.AsyncInterceptor/master.svg?label=Coveralls)](https://coveralls.io/r/JSkimming/Castle.Core.AsyncInterceptor \"Coveralls Coverage Status\")\n[![Latest release](https://img.shields.io/github/release/JSkimming/Castle.Core.AsyncInterceptor.svg)](https://github.com/JSkimming/Castle.Core.AsyncInterceptor/releases \"Latest release\")\n\u003c!--[![Coverity Scan Status](https://img.shields.io/coverity/scan/4829.svg)](https://scan.coverity.com/projects/4829 \"Coverity Scan Status\")--\u003e\n\n## What is AsyncInterceptor?\n\n__AsyncInterceptor__ is an extension to [Castle DynamicProxy](http://www.castleproject.org/projects/dynamicproxy/) to\nsimplify the development of interceptors for asynchronous methods.\n\n## Why do I want intercept methods?\n\nThe whys and wherefores of implementing interceptors is lengthy discussion, and beyond the scope of this introduction.\nA very common scenario is in the implementation of\n[Aspect-oriented patterns](https://en.wikipedia.org/wiki/Aspect-oriented_programming), for which exception handling is\nuseful use case.\n\nAn interceptor that catches exceptions and logs them could be implemented quite simply as:\n\n```csharp\n// Intercept() is the single method of IInterceptor.\npublic void Intercept(IInvocation invocation)\n{\n    try\n    {\n        invocation.Proceed();\n    }\n    catch (Exception ex)\n    {\n        Log.Error($\"Error calling {invocation.Method.Name}.\", ex);\n        throw;\n    }\n}\n```\n\n## What's not simple about asynchronous method interception?\n\nWhen implementing `IInterceptor` the underlying method is invoked like this:\n\n```csharp\npublic void Intercept(IInvocation invocation)\n{\n    // Step 1. Do something prior to invocation.\n\n    invocation.Proceed();\n\n    // Step 2. Do something after invocation.\n}\n```\n\nFor synchronous methods `Proceed()` returns only when the underlying method completes, but for asynchronous methods,\n(those that return [`Task`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx) or\n[`Task\u003cTResult\u003e`](https://msdn.microsoft.com/en-us/library/dd321424.aspx))\nthe `Proceed()` returns as soon as the underlying method hits an `await` (or `ContinueWith`).\n\nTherefore with asynchronous methods _Step 2_ is executed before the underlying methods logically completes.\n\n## How to intercept asynchronous methods without AsyncInterceptor?\n\nTo demonstrate how __AsyncInterceptor__ simplifies the interception of asynchronous methods, let's show how to do it\nwithout __AsyncInterceptor__.\n\n### Methods that return [`Task`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx)\n\nTo intercept methods that return a [`Task`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx)\n(__Note:__ it must be a [`Task`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx) not\n[`Task\u003cTResult\u003e`](https://msdn.microsoft.com/en-us/library/dd321424.aspx)) is not overly complicated.\n\nThe invocation provides access to the return value. By checking the type of the return value it is possible to await\nthe completion of the [`Task`](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx).\n\n```csharp\npublic void Intercept(IInvocation invocation)\n{\n    // Step 1. Do something prior to invocation.\n\n    invocation.Proceed();\n    Type type = invocation.ReturnValue?.GetType();\n    if (type != null \u0026\u0026 type == typeof(Task))\n    {\n        // Given the method returns a Task, wait for it to complete before performing Step 2\n        Func\u003cTask\u003e continuation = async () =\u003e\n        {\n            await (Task) invocation.ReturnValue;\n\n            // Step 2. Do something after invocation.\n        };\n\n        invocation.ReturnValue = continuation();\n        return;\n    }\n\n    // Assume the method is synchronous.\n\n    // Step 2. Do something after invocation.\n}\n```\n\n### Methods that return [`Task\u003cTResult\u003e`](https://msdn.microsoft.com/en-us/library/dd321424.aspx)\n\nTo intercept methods that return a [`Task\u003cTResult\u003e`](https://msdn.microsoft.com/en-us/library/dd321424.aspx) is far\nfrom simple. It's the reason why I created this library. Rather than go into the detail (the solution requires the use\nof reflection) the stack overflow question [Intercept async method that returns generic Task\u003c\u003e via\nDynamicProxy](https://stackoverflow.com/a/43272955) provides great overview.\n\n## How to intercept asynchronous methods __with__ AsyncInterceptor?\n\nIf you've got this far, then it's probably safe to assume you want to intercept asynchronous methods, and the options\nfor doing it manually look like a lot of work.\n\n### Option 1:  Implement `IAsyncInterceptor` interface to intercept invocations\n\nCreate a class that implements `IAsyncInterceptor`, then register it for interception in the same way as `IInterceptor`\nusing the ProxyGenerator extension methods, e.g.\n\n```csharp\nvar myClass = new ClasThatImplementsIMyInterface();\nvar generator = new ProxyGenerator();\nvar interceptor = new ClasThatImplementsIAsyncInterceptor();\nIMyInterface proxy = generator.CreateInterfaceProxyWithTargetInterface\u003cIMyInterface\u003e(myClass, interceptor)\n```\n\nImplementing\n[IAsyncInterceptor](https://github.com/JSkimming/Castle.Core.AsyncInterceptor/blob/master/src/Castle.Core.AsyncInterceptor/IAsyncInterceptor.cs)\nis the closest to traditional interception when implementing\n[IInterceptor](https://github.com/castleproject/Core/blob/master/src/Castle.Core/DynamicProxy/IInterceptor.cs)\n\nInstead of a single `void Intercept(IInvocation invocation)` method to implement, there are three:\n\n```csharp\nvoid InterceptSynchronous(IInvocation invocation);\nvoid InterceptAsynchronous(IInvocation invocation);\nvoid InterceptAsynchronous\u003cTResult\u003e(IInvocation invocation);\n```\n\n#### `InterceptSynchronous(IInvocation invocation)`\n\n`InterceptSynchronous` is effectively the same as `IInterceptor.Intercept`, though it is only called for synchronous\nmethods, e.g. methods that do not return `Task` or `Task\u003cTResult\u003e`.\n\nImplementing `InterceptSynchronous` could look something like this:\n\n```csharp\npublic void InterceptSynchronous(IInvocation invocation)\n{\n    // Step 1. Do something prior to invocation.\n\n    invocation.Proceed();\n\n    // Step 2. Do something after invocation.\n}\n```\n\n#### `InterceptAsynchronous(IInvocation invocation)`\n\n`InterceptAsynchronous(IInvocation invocation)` is called for methods that return `Task` but not the generic\n`Task\u003cTResult\u003e`.\n\nImplementing `InterceptAsynchronous(IInvocation invocation)` could look something like this:\n\n```csharp\npublic void InterceptAsynchronous(IInvocation invocation)\n{\n    invocation.ReturnValue = InternalInterceptAsynchronous(invocation);\n}\n\nprivate async Task InternalInterceptAsynchronous(IInvocation invocation)\n{\n    // Step 1. Do something prior to invocation.\n\n    invocation.Proceed();\n    var task = (Task)invocation.ReturnValue;\n    await task;\n\n    // Step 2. Do something after invocation.\n}\n```\n\n#### `InterceptAsynchronous\u003cTResult\u003e(IInvocation invocation)`\n\n`InterceptAsynchronous\u003cTResult\u003e(IInvocation invocation)` is called for methods that return the generic `Task\u003cTResult\u003e`.\n\nImplementing `InterceptAsynchronous\u003cTResult\u003e(IInvocation invocation)` could look something like this:\n\n```csharp\npublic void InterceptAsynchronous\u003cTResult\u003e(IInvocation invocation)\n{\n    invocation.ReturnValue = InternalInterceptAsynchronous\u003cTResult\u003e(invocation);\n}\n\nprivate async Task\u003cTResult\u003e InternalInterceptAsynchronous\u003cTResult\u003e(IInvocation invocation)\n{\n    // Step 1. Do something prior to invocation.\n\n    invocation.Proceed();\n    var task = (Task\u003cTResult\u003e)invocation.ReturnValue;\n    TResult result = await task;\n\n    // Step 2. Do something after invocation.\n\n    return result;\n}\n```\n\n### Option 2: Extend `AsyncInterceptorBase` class to intercept invocations\n\n#### __:warning: PROCEED WITH CAUTION :warning: This option come with a major caveat.__\n\n\u003e If you need to perform asynchronous operations before calling proceed, then you should implement option 1, otherwise\n\u003e you risk  thread starvation and deadlocking.\n\nCreate a class that extends the abstract base class `AsyncInterceptorBase`, then register it for interception in the same way as `IInterceptor` using the ProxyGenerator extension methods, e.g.\n\n```csharp\nvar myClass = new ClasThatImplementsIMyInterface();\nvar generator = new ProxyGenerator();\nvar interceptor = new ClasThatExtendsAsyncInterceptorBase();\nIMyInterface proxy = generator.CreateInterfaceProxyWithTargetInterface\u003cIMyInterface\u003e(myClass, interceptor)\n```\n\nExtending `AsyncInterceptorBase` provides a simple mechanism to intercept methods using the __async/await__ pattern. There are two abstract methods that must be implemented.\n\n```csharp\nTask InterceptAsync(IInvocation invocation, Func\u003cIInvocation, Task\u003e proceed);\nTask\u003cT\u003e InterceptAsync\u003cT\u003e(IInvocation invocation, Func\u003cIInvocation, Task\u003cT\u003e\u003e proceed);\n```\n\nEach method takes two parameters. The `IInvocation` provided by __DaynamicProxy__ and a proceed function to execute the invocation returning an awaitable task.\n\nThe first method in called when intercepting `void` methods or methods that return `Task`. The second method is called when intercepting any method that returns a value, including `Task\u003cTResult\u003e`.\n\nA possible extension of `AsyncInterceptorBase` for exception handling could be implemented as follows:\n\n```csharp\npublic class ExceptionHandlingInterceptor : AsyncInterceptorBase\n{\n    protected override async Task InterceptAsync(IInvocation invocation, Func\u003cIInvocation, Task\u003e proceed)\n    {\n        try\n        {\n            // Cannot simply return the the task, as any exceptions would not be caught below.\n            await proceed(invocation).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            Log.Error($\"Error calling {invocation.Method.Name}.\", ex);\n            throw;\n        }\n    }\n\n    protected override async Task\u003cT\u003e InterceptAsync\u003cT\u003e(IInvocation invocation, Func\u003cIInvocation, Task\u003cT\u003e\u003e proceed)\n    {\n        try\n        {\n            // Cannot simply return the the task, as any exceptions would not be caught below.\n            return await proceed(invocation).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            Log.Error($\"Error calling {invocation.Method.Name}.\", ex);\n            throw;\n        }\n    }\n}\n```\n\n### Option 3: Extend `ProcessingAsyncInterceptor\u003cTState\u003e` class to intercept invocations\n\nCreate a class that extends the abstract base class `ProcessingAsyncInterceptor\u003cTState\u003e`, then register it for\ninterception in the same was as `IInterceptor` using the ProxyGenerator extension methods, e.g.\n\n```csharp\nvar myClass = new ClasThatImplementsIMyInterface();\nvar generator = new ProxyGenerator();\nvar interceptor = new ClasThatExtendsProcessingAsyncInterceptor();\nIMyInterface proxy = generator.CreateInterfaceProxyWithTargetInterface\u003cIMyInterface\u003e(myClass, interceptor)\n```\n\nExtending\n[`ProcessingAsyncInterceptor\u003cTState\u003e`](https://github.com/JSkimming/Castle.Core.AsyncInterceptor/blob/documentation/src/Castle.Core.AsyncInterceptor/ProcessingAsyncInterceptor.cs),\nprovides a simplified mechanism of intercepting method invocations without having to implement the three methods of\n`IAsyncInterceptor`.\n\n`ProcessingAsyncInterceptor\u003cTState\u003e` defines two virtual methods, one that is invoked before to the method invocation,\nthe second after.\n\n```csharp\nprotected virtual TState StartingInvocation(IInvocation invocation);\nprotected virtual void CompletedInvocation(IInvocation invocation, TState state);\n```\n\nState can be maintained between the two method through the generic class parameter `TState`. `StartingInvocation` is\ncalled before method invocation. The return value of type `TState` is then passed to the `CompletedInvocation` which is\ncalled after method invocation.\n\nA possible extension of `ProcessingAsyncInterceptor\u003cTState\u003e` could be as follows:\n\n```csharp\npublic class MyProcessingAsyncInterceptor : ProcessingAsyncInterceptor\u003cstring\u003e\n{\n    protected override string StartingInvocation(IInvocation invocation)\n    {\n        return $\"{invocation.Method.Name}:StartingInvocation:{DateTime.UtcNow:O}\";\n    }\n\n    protected override void CompletedInvocation(IInvocation invocation, string state)\n    {\n        Trace.WriteLine(state);\n        Trace.WriteLine($\"{invocation.Method.Name}:CompletedInvocation:{DateTime.UtcNow:O}\");\n    }\n}\n```\n\nThe state of type `TState` returned from `StartingInvocation` can be `null`. Neither `StartingInvocation` nor\n`CompletedInvocation` are require to be overridden in the class that derives from `ProcessingAsyncInterceptor\u003cTState\u003e`.\nThe default implementation of StartingInvocation simply returns `null`. If all you require is to intercept methods\nafter they are invoked, then just implement `CompletedInvocation` and ignore the `state` parameter which will be\nnull. In that situation your class can be defined as:\n\n```csharp\npublic class MyProcessingAsyncInterceptor : ProcessingAsyncInterceptor\u003cobject\u003e\n{\n    protected override void CompletedInvocation(IInvocation invocation, object state)\n    {\n        Trace.WriteLine($\"{invocation.Method.Name}:CompletedInvocation:{DateTime.UtcNow:O}\");\n    }\n}\n```\n\n## Using AsyncInterceptor with non-overloaded `ProxyGenerator` methods\n\nWhile AsyncInterceptor offers convenient overloads for the most common `ProxyGenerator` methods, some methods do not (yet) have such overloads. In this case, the extension method `IAsyncInterceptor.ToInterceptor()` can be used to obtain a regular `IInterceptor` implementation.\n\n```csharp\nvar generator = new ProxyGenerator();\nvar interceptor = new MyInterceptorWithoutTarget\u003cT\u003e();\ngenerator.CreateInterfaceProxyWithoutTarget(typeof(T), interceptor.ToInterceptor());\n```\n\n## Method invocation timing using `AsyncTimingInterceptor`\n\nA common use-case for method invocation interception is to time how long a method takes to execute. For this reason\n[`AsyncTimingInterceptor`](https://github.com/JSkimming/Castle.Core.AsyncInterceptor/blob/documentation/src/Castle.Core.AsyncInterceptor/AsyncTimingInterceptor.cs)\nis provided.\n\n`AsyncTimingInterceptor` is a specialised implementation of `ProcessingAsyncInterceptor\u003cTState\u003e` that uses a\n[Stopwatch](https://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx) as the `TState`.\n\n`AsyncTimingInterceptor` defines two abstract methods, one that is invoked before method invocation and before the\nStopwatch has started. The second after method invocation and the Stopwatch has stopped\n\n```csharp\nprotected abstract void StartingTiming(IInvocation invocation);\nprotected abstract void CompletedTiming(IInvocation invocation, Stopwatch stopwatch);\n```\n\nA possible extension of `AsyncTimingInterceptor` could be as follows:\n\n```csharp\npublic class TestAsyncTimingInterceptor : AsyncTimingInterceptor\n{\n    protected override void StartingTiming(IInvocation invocation)\n    {\n        Trace.WriteLine($\"{invocation.Method.Name}:StartingTiming\");\n    }\n\n    protected override void CompletedTiming(IInvocation invocation, Stopwatch stopwatch)\n    {\n        Trace.WriteLine($\"{invocation.Method.Name}:CompletedTiming:{stopwatch.Elapsed:g}\");\n    }\n}\n```\n\n## Testing\n\nThis library maintains a high level of code coverage with extensive unit tests.\n\n### Running the tests\n\nThere are several ways to run the tests, the most convenient is through Visual Studio, via Test Explorer or ReSharper.\n\nThe tests can also be executed from the command line like this:\n\n```bash\ndotnet test test/Castle.Core.AsyncInterceptor.Tests/Castle.Core.AsyncInterceptor.Tests.csproj\n```\n\nOn Windows the above command will execute the tests on all 3 runtimes, .NETFramework,Version=v4.7,\n.NETCoreApp,Version=v1.1, and .NETCoreApp,Version=v2.1.\n\nTo run the tests targeting a specific runtime (which may be necessary if you don't have all them all installed) run the\nfollowing command:\n\n```bash\ndotnet test -f netcoreapp3.1 test/Castle.Core.AsyncInterceptor.Tests/Castle.Core.AsyncInterceptor.Tests.csproj\n```\n\nA docker compose file is provided to run the tests on a linux container. To execute the tests in a container run the\nfollowing command:\n\n```bash\ndocker build --target test --progress=plain .\n```\n\n### Executing the code coverage tests\n\nCode coverage uses the excellent and free [OpenCover](https://github.com/OpenCover/opencover \"OpenCover Repository\").\n\nTo execute the tests with code coverage (Windows Only) run the following command:\n\n```cmd\ncoverage.cmd\n```\n\nCode coverage reports are produced using\n[ReportGenerator](https://github.com/danielpalme/ReportGenerator \"ReportGenerator Repository\") and can be viewed in the\n`test/TestResults/Report` folder.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJSkimming%2FCastle.Core.AsyncInterceptor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJSkimming%2FCastle.Core.AsyncInterceptor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJSkimming%2FCastle.Core.AsyncInterceptor/lists"}