diff --git a/src/Foil.Logging/Foil.Logging.csproj b/src/Foil.Logging/Foil.Logging.csproj
new file mode 100644
index 0000000..bcc8d54
--- /dev/null
+++ b/src/Foil.Logging/Foil.Logging.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Library
+ 1.0.0
+ netstandard1.6;netstandard2.0
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Foil.Logging/InvocationExtensions.cs b/src/Foil.Logging/InvocationExtensions.cs
new file mode 100644
index 0000000..93fbcb1
--- /dev/null
+++ b/src/Foil.Logging/InvocationExtensions.cs
@@ -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";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Foil.Logging/LoggingInterceptor.cs b/src/Foil.Logging/LoggingInterceptor.cs
new file mode 100644
index 0000000..3dd63bc
--- /dev/null
+++ b/src/Foil.Logging/LoggingInterceptor.cs
@@ -0,0 +1,33 @@
+using System;
+using Castle.DynamicProxy;
+using Microsoft.Extensions.Logging;
+
+namespace Foil.Logging
+{
+ public sealed class LoggingInterceptor : IInterceptor
+ {
+ private readonly ILogger _logger;
+
+ public LoggingInterceptor(ILogger 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Foil.Sample/Foil.Sample.csproj b/src/Foil.Sample/Foil.Sample.csproj
index 0a4734a..7897437 100755
--- a/src/Foil.Sample/Foil.Sample.csproj
+++ b/src/Foil.Sample/Foil.Sample.csproj
@@ -1,14 +1,18 @@
Exe
- netcoreapp1.1
+ netcoreapp1.1;netcoreapp2.0
-
-
-
+
+
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/src/Foil.Sample/Program.cs b/src/Foil.Sample/Program.cs
index 6a8a875..d8005ce 100755
--- a/src/Foil.Sample/Program.cs
+++ b/src/Foil.Sample/Program.cs
@@ -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
{
@@ -10,12 +12,16 @@ static void Main(string[] args)
{
var services = new ServiceCollection();
- services.AddTransientWithInterception(m => m.InterceptBy());
+ services.AddLogging();
+ services.AddTransientWithInterception(m => m.InterceptBy().ThenBy());
var provider = services.BuildServiceProvider();
+ var loggingFactory = provider.GetRequiredService();
+ loggingFactory.AddConsole(LogLevel.Trace);
var service = provider.GetRequiredService();
- service.Call();
+
+ service.Call("Dear User");
}
}
}
\ No newline at end of file
diff --git a/src/Foil.Sample/Services/ISampleService.cs b/src/Foil.Sample/Services/ISampleService.cs
index e104176..d1ee3b7 100644
--- a/src/Foil.Sample/Services/ISampleService.cs
+++ b/src/Foil.Sample/Services/ISampleService.cs
@@ -2,6 +2,6 @@
{
public interface ISampleService
{
- void Call();
+ void Call(string sample);
}
}
\ No newline at end of file
diff --git a/src/Foil.Sample/Services/SampleService.cs b/src/Foil.Sample/Services/SampleService.cs
index c59ed3d..36af21e 100644
--- a/src/Foil.Sample/Services/SampleService.cs
+++ b/src/Foil.Sample/Services/SampleService.cs
@@ -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}");
}
}
}
\ No newline at end of file
diff --git a/src/Foil.UnitTests/Foil.UnitTests.csproj b/src/Foil.UnitTests/Foil.UnitTests.csproj
index ec28b2d..297e84f 100755
--- a/src/Foil.UnitTests/Foil.UnitTests.csproj
+++ b/src/Foil.UnitTests/Foil.UnitTests.csproj
@@ -1,16 +1,16 @@
- netcoreapp1.1
+ netcoreapp1.1;netcoreapp2.0
-
-
-
-
-
-
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/Foil.sln b/src/Foil.sln
index 34c1608..9e12c56 100644
--- a/src/Foil.sln
+++ b/src/Foil.sln
@@ -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
@@ -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
diff --git a/src/Foil/AsyncInterceptor.cs b/src/Foil/AsyncInterceptor.cs
new file mode 100644
index 0000000..d0ace65
--- /dev/null
+++ b/src/Foil/AsyncInterceptor.cs
@@ -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 GenericSynchronousHandlers =
+ new ConcurrentDictionary
+ {
+ [typeof(void)] = InterceptSynchronousVoid
+ };
+
+ private static readonly MethodInfo HandleAsyncMethodInfo =
+ typeof(AsyncInterceptor)
+ .GetMethod(nameof(HandleAsyncWithResult), BindingFlags.Static | BindingFlags.NonPublic);
+
+ private static readonly ConcurrentDictionary GenericAsyncHandlers =
+ new ConcurrentDictionary();
+
+ 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(IInvocation invocation,
+ AsyncInterceptor asyncInterceptor)
+ {
+ asyncInterceptor.InterceptAsynchronous(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(IInvocation invocation)
+ {
+ invocation.ReturnValue = InterceptAsync(invocation, ProceedAsynchronous);
+ }
+
+ protected abstract Task InterceptAsync(IInvocation invocation, Func proceed);
+
+ protected abstract Task InterceptAsync(
+ IInvocation invocation,
+ Func> 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(AsyncInterceptor me, IInvocation invocation)
+ {
+ Task task = me.InterceptAsync(invocation, ProceedSynchronous);
+
+ 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 ProceedSynchronous(IInvocation invocation)
+ {
+ try
+ {
+ invocation.Proceed();
+ return Task.FromResult((TResult) invocation.ReturnValue);
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+ }
+
+ private static async Task ProceedAsynchronous(IInvocation invocation)
+ {
+ invocation.Proceed();
+ var originalReturnValue = (Task) invocation.ReturnValue;
+ await originalReturnValue.ConfigureAwait(false);
+ }
+
+ private static async Task ProceedAsynchronous(IInvocation invocation)
+ {
+ invocation.Proceed();
+ var originalReturnValue = (Task) 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Foil/Conventions/ConvensionBasedProxyGenerationHook.cs b/src/Foil/Conventions/ConventionBasedProxyGenerationHook.cs
similarity index 80%
rename from src/Foil/Conventions/ConvensionBasedProxyGenerationHook.cs
rename to src/Foil/Conventions/ConventionBasedProxyGenerationHook.cs
index 820a5f1..ad71aea 100644
--- a/src/Foil/Conventions/ConvensionBasedProxyGenerationHook.cs
+++ b/src/Foil/Conventions/ConventionBasedProxyGenerationHook.cs
@@ -2,15 +2,14 @@
using System.Reflection;
using Castle.DynamicProxy;
using Foil.Interceptions;
-using Microsoft.Extensions.Logging;
namespace Foil.Conventions
{
- public class ConvensionBasedProxyGenerationHook : IProxyGenerationHook
+ public class ConventionBasedProxyGenerationHook : IProxyGenerationHook
{
private readonly IMethodConvention _convention;
- public ConvensionBasedProxyGenerationHook(IMethodConvention convention)
+ public ConventionBasedProxyGenerationHook(IMethodConvention convention)
{
_convention = convention ?? throw new ArgumentNullException(nameof(convention));
}
diff --git a/src/Foil/Foil.csproj b/src/Foil/Foil.csproj
index 09da4f4..a7f4549 100644
--- a/src/Foil/Foil.csproj
+++ b/src/Foil/Foil.csproj
@@ -1,13 +1,13 @@
Library
- netcoreapp1.1
1.0.0
+ netstandard1.6;netstandard2.0
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/Foil/Interceptions/InterceptionOptions.cs b/src/Foil/Interceptions/InterceptionOptions.cs
index 560f4c1..ebf92b8 100644
--- a/src/Foil/Interceptions/InterceptionOptions.cs
+++ b/src/Foil/Interceptions/InterceptionOptions.cs
@@ -17,7 +17,7 @@ public class InterceptionOptions : IInterceptBy, IThenInterceptBy
public IThenInterceptBy ThenBy() where TInterceptor : IInterceptor
{
- if (_interceptors.ContainsKey(typeof(TInterceptor)))
+ if (!_interceptors.ContainsKey(typeof(TInterceptor)))
_interceptors.Add(typeof(TInterceptor), typeof(TInterceptor));
return this;
diff --git a/src/Foil/ServiceCollectionExtensions.cs b/src/Foil/ServiceCollectionExtensions.cs
index 914735a..2169ebb 100644
--- a/src/Foil/ServiceCollectionExtensions.cs
+++ b/src/Foil/ServiceCollectionExtensions.cs
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Castle.DynamicProxy;
-using Castle.DynamicProxy.Generators;
using Foil.Conventions;
using Foil.Interceptions;
using Microsoft.Extensions.DependencyInjection;
@@ -12,45 +11,97 @@ namespace Foil
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTransientWithInterception(
- this IServiceCollection services, Action action)
+ this IServiceCollection services,
+ Func serviceFactory,
+ Action configurator)
+ where T : class where TImplementation : class, T
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+ if (serviceFactory == null)
+ throw new ArgumentNullException(nameof(serviceFactory));
+
+ return services.Add(
+ lifetime => new ServiceDescriptor(typeof(TImplementation), serviceFactory, lifetime),
+ configurator, ServiceLifetime.Transient);
+ }
+
+ public static IServiceCollection AddScopedWithInterception(this IServiceCollection services,
+ Func serviceFactory,
+ Action configurator)
+ where T : class where TImplementation : class, T
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+ if (serviceFactory == null)
+ throw new ArgumentNullException(nameof(serviceFactory));
+
+ return services.Add(
+ lifetime => new ServiceDescriptor(typeof(TImplementation), serviceFactory, lifetime),
+ configurator, ServiceLifetime.Scoped);
+ }
+
+ public static IServiceCollection AddSingletonWithInterception(
+ this IServiceCollection services,
+ Func serviceFactory,
+ Action configurator)
+ where T : class where TImplementation : class, T
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+ if (serviceFactory == null)
+ throw new ArgumentNullException(nameof(serviceFactory));
+
+ return services.Add(
+ lifetime => new ServiceDescriptor(typeof(TImplementation), serviceFactory, lifetime),
+ configurator, ServiceLifetime.Singleton);
+ }
+
+ public static IServiceCollection AddTransientWithInterception(
+ this IServiceCollection services, Action configurator)
where T : class
where TImplementation : class, T
{
- return AddWithInterception(services, action, ServiceLifetime.Transient);
+ return Add(services,
+ lifetime => ServiceDescriptor.Describe(typeof(TImplementation), typeof(TImplementation), lifetime),
+ configurator, ServiceLifetime.Transient);
}
public static IServiceCollection AddScopedWithInterception(
- this IServiceCollection services, Action action)
+ this IServiceCollection services, Action configurator)
where T : class
where TImplementation : class, T
{
- return AddWithInterception(services, action, ServiceLifetime.Scoped);
+ return Add(services,
+ lifetime => ServiceDescriptor.Describe(typeof(TImplementation), typeof(TImplementation), lifetime),
+ configurator, ServiceLifetime.Scoped);
}
public static IServiceCollection AddSingletonWithInterception(
- this IServiceCollection services, Action action)
+ this IServiceCollection services, Action configurator)
where T : class
where TImplementation : class, T
{
- return AddWithInterception(services, action, ServiceLifetime.Singleton);
+ if (services == null) throw new ArgumentNullException(nameof(services));
+ return Add(services,
+ lifetime => ServiceDescriptor.Describe(typeof(TImplementation), typeof(TImplementation), lifetime),
+ configurator, ServiceLifetime.Singleton);
}
- private static IServiceCollection AddWithInterception(this IServiceCollection services,
- Action action, ServiceLifetime lifetime)
- where T : class
- where TImplementation : class, T
+ private static IServiceCollection Add(this IServiceCollection services,
+ Func descriptorFactory, Action configurator,
+ ServiceLifetime lifetime) where TService : class where TImplementation : class, TService
{
- if (services == null) throw new ArgumentNullException(nameof(services));
- if (!Enum.IsDefined(typeof(ServiceLifetime), lifetime))
- throw new ArgumentOutOfRangeException(nameof(lifetime),
- "Value should be defined in the ServiceLifetime enum.");
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+ if (configurator == null) throw new ArgumentNullException(nameof(configurator));
+
var interceptionOptions = new InterceptionOptions();
- action?.Invoke(interceptionOptions);
+ configurator.Invoke(interceptionOptions);
interceptionOptions.Interceptors.ForEach(services.TryAddTransient);
-
- services.TryAdd(ServiceDescriptor.Describe(typeof(TImplementation), typeof(TImplementation), lifetime));
+ services.TryAdd(descriptorFactory(lifetime));
services.AddTransient(sp =>
{
@@ -58,14 +109,15 @@ private static IServiceCollection AddWithInterception(this I
.Select(sp.GetRequiredService)
.Cast()
.ToArray();
-
+
var implementation = sp.GetRequiredService();
var proxyFactory = new ProxyGenerator();
- var proxyGenerationHook = new ConvensionBasedProxyGenerationHook(interceptionOptions.Convention);
+ var proxyGenerationHook = new ConventionBasedProxyGenerationHook(interceptionOptions.Convention);
var proxyGenerationOptions = new ProxyGenerationOptions(proxyGenerationHook);
-
- return proxyFactory.CreateInterfaceProxyWithTarget(implementation, proxyGenerationOptions, interceptorInstances);
+
+ return proxyFactory.CreateInterfaceProxyWithTarget(implementation, proxyGenerationOptions,
+ interceptorInstances);
});
return services;