-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from moattarwork/Extension
Included async interception and fix the bug with multiple in…
- Loading branch information
Showing
14 changed files
with
378 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Library</OutputType> | ||
<PackageVersion>1.0.0</PackageVersion> | ||
<TargetFrameworks>netstandard1.6;netstandard2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Castle.Core" Version="4.3.1" /> | ||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
using System.Linq; | ||
using Castle.DynamicProxy; | ||
using Newtonsoft.Json; | ||
|
||
namespace Foil.Logging | ||
{ | ||
public static class InvocationExtensions | ||
{ | ||
public static string FormatArguments(this IInvocation invocation) | ||
{ | ||
if (invocation == null) throw new ArgumentNullException(nameof(invocation)); | ||
|
||
var arguments = invocation.Arguments; | ||
if (!arguments.Any()) | ||
return string.Empty; | ||
|
||
|
||
var serializedArguments = arguments | ||
.Select((a, index) => $"Arg[{index}]: {a.ToJsonOrDefault()}") | ||
.ToList(); | ||
return string.Join(Environment.NewLine, serializedArguments); | ||
|
||
} | ||
|
||
private static string ToJsonOrDefault(this object obj) | ||
{ | ||
if (obj == null) throw new ArgumentNullException(nameof(obj)); | ||
try | ||
{ | ||
return JsonConvert.SerializeObject(obj); | ||
} | ||
catch | ||
{ | ||
return "Error in serializing argument to json"; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
using Castle.DynamicProxy; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Foil.Logging | ||
{ | ||
public sealed class LoggingInterceptor : IInterceptor | ||
{ | ||
private readonly ILogger<LoggingInterceptor> _logger; | ||
|
||
public LoggingInterceptor(ILogger<LoggingInterceptor> logger) | ||
{ | ||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||
} | ||
|
||
public void Intercept(IInvocation invocation) | ||
{ | ||
_logger.LogInformation($"Executing Method: {invocation.Method.Name}"); | ||
|
||
LogInvocationInfo(invocation); | ||
|
||
invocation.Proceed(); | ||
|
||
_logger.LogInformation($"Executed Method: {invocation.Method.Name}"); | ||
} | ||
|
||
private void LogInvocationInfo(IInvocation invocation) | ||
{ | ||
if (_logger.IsEnabled(LogLevel.Trace)) | ||
_logger.LogTrace(invocation.FormatArguments()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp1.1</TargetFramework> | ||
<TargetFrameworks>netcoreapp1.1;netcoreapp2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Castle.Core" Version="4.1.1"/> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1"/> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1"/> | ||
<PackageReference Include="Castle.Core" Version="4.3.1" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | ||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\Foil\Foil.csproj"/> | ||
<ProjectReference Include="..\Foil.Logging\Foil.Logging.csproj" /> | ||
<ProjectReference Include="..\Foil\Foil.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,6 @@ | |
{ | ||
public interface ISampleService | ||
{ | ||
void Call(); | ||
void Call(string sample); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>netcoreapp1.1</TargetFramework> | ||
<TargetFrameworks>netcoreapp1.1;netcoreapp2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="4.19.4"/> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2"/> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"/> | ||
<PackageReference Include="NSubstitute" Version="2.0.3"/> | ||
<PackageReference Include="xunit" Version="2.2.0"/> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0"/> | ||
<PackageReference Include="FluentAssertions" Version="5.4.2" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> | ||
<PackageReference Include="NSubstitute" Version="3.1.0" /> | ||
<PackageReference Include="xunit" Version="2.2.0" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\Foil\Foil.csproj"/> | ||
<ProjectReference Include="..\Foil\Foil.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using Castle.DynamicProxy; | ||
|
||
namespace Foil | ||
{ | ||
public abstract class AsyncInterceptor : IInterceptor | ||
{ | ||
private static readonly MethodInfo InterceptSynchronousMethodInfo = | ||
typeof(AsyncInterceptor) | ||
.GetMethod(nameof(InterceptSynchronousResult), BindingFlags.Static | BindingFlags.NonPublic); | ||
|
||
private static readonly ConcurrentDictionary<Type, GenericSynchronousHandler> GenericSynchronousHandlers = | ||
new ConcurrentDictionary<Type, GenericSynchronousHandler> | ||
{ | ||
[typeof(void)] = InterceptSynchronousVoid | ||
}; | ||
|
||
private static readonly MethodInfo HandleAsyncMethodInfo = | ||
typeof(AsyncInterceptor) | ||
.GetMethod(nameof(HandleAsyncWithResult), BindingFlags.Static | BindingFlags.NonPublic); | ||
|
||
private static readonly ConcurrentDictionary<Type, GenericAsyncHandler> GenericAsyncHandlers = | ||
new ConcurrentDictionary<Type, GenericAsyncHandler>(); | ||
|
||
public void Intercept(IInvocation invocation) | ||
{ | ||
var methodType = GetMethodType(invocation.Method.ReturnType); | ||
|
||
switch (methodType) | ||
{ | ||
case MethodType.AsyncAction: | ||
InterceptAsynchronous(invocation); | ||
return; | ||
case MethodType.AsyncFunction: | ||
GetHandler(invocation.Method.ReturnType).Invoke(invocation, this); | ||
return; | ||
default: | ||
InterceptSynchronous(invocation); | ||
return; | ||
} | ||
} | ||
|
||
private static MethodType GetMethodType(Type returnType) | ||
{ | ||
if (returnType == typeof(void) || !typeof(Task).IsAssignableFrom(returnType)) | ||
return MethodType.Synchronous; | ||
|
||
return returnType.GetTypeInfo().IsGenericType ? MethodType.AsyncFunction : MethodType.AsyncAction; | ||
} | ||
|
||
private static GenericAsyncHandler GetHandler(Type returnType) | ||
{ | ||
var handler = GenericAsyncHandlers.GetOrAdd(returnType, CreateHandlerAsync); | ||
return handler; | ||
} | ||
|
||
private static void HandleAsyncWithResult<TResult>(IInvocation invocation, | ||
AsyncInterceptor asyncInterceptor) | ||
{ | ||
asyncInterceptor.InterceptAsynchronous<TResult>(invocation); | ||
} | ||
|
||
public void InterceptSynchronous(IInvocation invocation) | ||
{ | ||
var returnType = invocation.Method.ReturnType; | ||
var handler = GenericSynchronousHandlers.GetOrAdd(returnType, CreateHandlerForSync); | ||
handler(this, invocation); | ||
} | ||
|
||
private static GenericSynchronousHandler CreateHandlerForSync(Type returnType) | ||
{ | ||
var method = InterceptSynchronousMethodInfo.MakeGenericMethod(returnType); | ||
return (GenericSynchronousHandler) method.CreateDelegate(typeof(GenericSynchronousHandler)); | ||
} | ||
|
||
private static GenericAsyncHandler CreateHandlerAsync(Type returnType) | ||
{ | ||
var taskReturnType = returnType.GetGenericArguments()[0]; | ||
var method = HandleAsyncMethodInfo.MakeGenericMethod(taskReturnType); | ||
return (GenericAsyncHandler) method.CreateDelegate(typeof(GenericAsyncHandler)); | ||
} | ||
|
||
public void InterceptAsynchronous(IInvocation invocation) | ||
{ | ||
invocation.ReturnValue = InterceptAsync(invocation, ProceedAsynchronous); | ||
} | ||
|
||
public void InterceptAsynchronous<TResult>(IInvocation invocation) | ||
{ | ||
invocation.ReturnValue = InterceptAsync(invocation, ProceedAsynchronous<TResult>); | ||
} | ||
|
||
protected abstract Task InterceptAsync(IInvocation invocation, Func<IInvocation, Task> proceed); | ||
|
||
protected abstract Task<TResult> InterceptAsync<TResult>( | ||
IInvocation invocation, | ||
Func<IInvocation, Task<TResult>> proceed); | ||
|
||
private static void InterceptSynchronousVoid(AsyncInterceptor me, IInvocation invocation) | ||
{ | ||
var task = me.InterceptAsync(invocation, ProceedSynchronous); | ||
|
||
if (!task.IsCompleted) Task.Run(() => task).Wait(); | ||
|
||
if (task.IsFaulted) throw task.Exception.InnerException; | ||
} | ||
|
||
private static void InterceptSynchronousResult<TResult>(AsyncInterceptor me, IInvocation invocation) | ||
{ | ||
Task task = me.InterceptAsync(invocation, ProceedSynchronous<TResult>); | ||
|
||
if (!task.IsCompleted) Task.Run(() => task).Wait(); | ||
|
||
if (task.IsFaulted) throw task.Exception.InnerException; | ||
} | ||
|
||
private static Task ProceedSynchronous(IInvocation invocation) | ||
{ | ||
try | ||
{ | ||
invocation.Proceed(); | ||
return Task.CompletedTask; | ||
} | ||
catch (Exception e) | ||
{ | ||
return Task.FromException(e); | ||
} | ||
} | ||
|
||
private static Task<TResult> ProceedSynchronous<TResult>(IInvocation invocation) | ||
{ | ||
try | ||
{ | ||
invocation.Proceed(); | ||
return Task.FromResult((TResult) invocation.ReturnValue); | ||
} | ||
catch (Exception e) | ||
{ | ||
return Task.FromException<TResult>(e); | ||
} | ||
} | ||
|
||
private static async Task ProceedAsynchronous(IInvocation invocation) | ||
{ | ||
invocation.Proceed(); | ||
var originalReturnValue = (Task) invocation.ReturnValue; | ||
await originalReturnValue.ConfigureAwait(false); | ||
} | ||
|
||
private static async Task<TResult> ProceedAsynchronous<TResult>(IInvocation invocation) | ||
{ | ||
invocation.Proceed(); | ||
var originalReturnValue = (Task<TResult>) invocation.ReturnValue; | ||
|
||
var result = await originalReturnValue.ConfigureAwait(false); | ||
return result; | ||
} | ||
|
||
private delegate void GenericSynchronousHandler(AsyncInterceptor me, IInvocation invocation); | ||
|
||
private delegate void GenericAsyncHandler(IInvocation invocation, AsyncInterceptor asyncInterceptor); | ||
|
||
private enum MethodType | ||
{ | ||
Synchronous, | ||
AsyncAction, | ||
AsyncFunction | ||
} | ||
} | ||
} |
Oops, something went wrong.