Skip to content

Commit

Permalink
Merge pull request #7 from moattarwork/Extension
Browse files Browse the repository at this point in the history
Included async interception and fix the bug with multiple in…
  • Loading branch information
moattarwork authored Nov 16, 2018
2 parents f9fc59d + 58405fd commit 6f86fbc
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 50 deletions.
16 changes: 16 additions & 0 deletions src/Foil.Logging/Foil.Logging.csproj
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>
39 changes: 39 additions & 0 deletions src/Foil.Logging/InvocationExtensions.cs
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";
}
}
}
}
33 changes: 33 additions & 0 deletions src/Foil.Logging/LoggingInterceptor.cs
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());
}
}
}
14 changes: 9 additions & 5 deletions src/Foil.Sample/Foil.Sample.csproj
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>
12 changes: 9 additions & 3 deletions src/Foil.Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Foil.Sample.Interceptors;
using Foil.Logging;
using Foil.Sample.Interceptors;
using Foil.Sample.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Foil.Sample
{
Expand All @@ -10,12 +12,16 @@ static void Main(string[] args)
{
var services = new ServiceCollection();

services.AddTransientWithInterception<ISampleService, SampleService>(m => m.InterceptBy<LogInterceptor>());
services.AddLogging();
services.AddTransientWithInterception<ISampleService, SampleService>(m => m.InterceptBy<LoggingInterceptor>().ThenBy<LogInterceptor>());

var provider = services.BuildServiceProvider();
var loggingFactory = provider.GetRequiredService<ILoggerFactory>();
loggingFactory.AddConsole(LogLevel.Trace);

var service = provider.GetRequiredService<ISampleService>();
service.Call();

service.Call("Dear User");
}
}
}
2 changes: 1 addition & 1 deletion src/Foil.Sample/Services/ISampleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface ISampleService
{
void Call();
void Call(string sample);
}
}
4 changes: 2 additions & 2 deletions src/Foil.Sample/Services/SampleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ namespace Foil.Sample.Services
{
public class SampleService : ISampleService
{
public virtual void Call()
public virtual void Call(string sample)
{
Console.WriteLine("Hello Sample");
Console.WriteLine($"Hello {sample}");
}
}
}
16 changes: 8 additions & 8 deletions src/Foil.UnitTests/Foil.UnitTests.csproj
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>
6 changes: 6 additions & 0 deletions src/Foil.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Foil.UnitTests", "Foil.Unit
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Foil.Sample", "Foil.Sample\Foil.Sample.csproj", "{3AD6C03C-F18A-4F95-9575-FC4B32D83F40}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Foil.Logging", "Foil.Logging\Foil.Logging.csproj", "{22710EAF-09BE-495D-A7EF-23F262ACF80A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -27,6 +29,10 @@ Global
{3AD6C03C-F18A-4F95-9575-FC4B32D83F40}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AD6C03C-F18A-4F95-9575-FC4B32D83F40}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AD6C03C-F18A-4F95-9575-FC4B32D83F40}.Release|Any CPU.Build.0 = Release|Any CPU
{22710EAF-09BE-495D-A7EF-23F262ACF80A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22710EAF-09BE-495D-A7EF-23F262ACF80A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22710EAF-09BE-495D-A7EF-23F262ACF80A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22710EAF-09BE-495D-A7EF-23F262ACF80A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
173 changes: 173 additions & 0 deletions src/Foil/AsyncInterceptor.cs
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
}
}
}
Loading

0 comments on commit 6f86fbc

Please sign in to comment.