.NET proxy generator powered by Roslyn
$ dotnet add package ProxyGen.NET.NET proxy generator powered by Roslyn
This documentation refers the version 8.X of the library
This library currently supports generating proxies for interface interception and duck typing.
using Solti.Utils.Proxy;
...
public class MyInterceptor: InterfaceInterceptor<IMyInterface>
{
public MyInterceptor(IMyInterface target) : base(target) {}
public MyInterceptor(IMyInterface target, MyParam myParam) : base(target) {} // overloaded constructor
public override object? Invoke(InvocationContext context) // Invoking the generated proxy instance will trigger this method
{
if (suppressOriginalMethod)
{
return something;
// ref|out parameters can be assigned by setting the corresponding "context.Args[]" item
}
context.Args[0] = someNewVal; // "someNewVal" will be forwarded to the original method
return base.Invoke(context); // Let the original method do its work
}
}
// OR
public class MyInterceptorTargetingTheImplementation: InterfaceInterceptor<IMyInterface, MyInterfaceImplementation>
{
public MyInterceptor(MyInterfaceImplementation target) : base(target) {}
public override object? Invoke(InvocationContext context)
{
MemberInfo
ifaceMember = context.InterfaceMember, // Will point to the invoked IMyInterface member (e.g.: IMyInterface.Foo())
targetMember = context.TargetMember; // Will point to the underlying MyInterfaceImplementation member (e.g. MyInterfaceImplementation.Foo())
return base.Invoke(context);
}
}
using System;
...
IMyInterface target = new MyClass();
...
IMyInterface proxy;
proxy = ProxyGenerator<IMyInterface, MyInterceptor>.Activate(Tuple.Create(target)); // or ActivateAsync()
proxy = ProxyGenerator<IMyInterface, MyInterceptor>.Activate(Tuple.Create(target, new MyParam()));Note that the target can access its most outer enclosing proxy. To achieve this it just has to implement the IProxyAccess<IMyInterface> interface:
using Solti.Utils.Proxy;
public class MyClass : IMyInterface, IProxyAccess<IMyInterface>
{
...
public IMyInterface Proxy { get; set; }
}public class TargetClass // does not implement IDuck
{
public void Foo(){...}
}
...
public interface IDuck
{
void Foo();
}using Solti.Utils.Proxy.Generators;
...
TargetClass target = ...;
IDuck duck = DuckGenerator<IDuck, TargetClass>.Activate(Tuple.Create(target)); // or ActivateAsync()Related tests can be seen here.
By setting the ProxyGen.AssemblyCacheDir property in YourApp.runtimeconfig.json you can make the system cache the generated assembly, so next time your app starts and requests the proxy there won't be time consuming emitting operation.
You can do it easily by creating a template file named runtimeconfig.template.json in your project folder:
{
"configProperties": {
"ProxyGen.AssemblyCacheDir": "GeneratedAssemblies"
}
}This library can be used as a source generator so you can embed the generated proxy type into the assembly that uses it. This is simply done by the Solti.Utils.Proxy.Attributes.EmbedGeneratedTypeAttribute:
[assembly: EmbedGeneratedType(typeof(ProxyGenerator<IMyInterface, MyInterceptor<IMyInterface>>))]
[assembly: EmbedGeneratedType(typeof(DuckGenerator<IMyInterface, MyClass>))]
The xXxGenerator.GetGeneratedType() method returns the embedded type if it is present in the assembly in which the GetGeneratedType() was called. Since all the time consumig operations already happened in compile time, requesting embedded types can singificantly improve the performance.
Note that:
-filter:-[*]Proxies.GeneratedClass_* switch)YourProject\Solti.Utils.Proxy\Solti.Utils.Proxy.Internals.ProxyEmbedder\Proxies.GeneratedClass_XxX.cs)<ItemGroup>
<Compile Remove="Solti.Utils.Proxy\**" />
<EmbeddedResource Remove="Solti.Utils.Proxy\**" />
<None Remove="Solti.Utils.Proxy\**" />
</ItemGroup>
ProxyGen is able to dump the generated sources. Due to performance considerations it is disabled by default. To enable
In runtime:
Set the ProxyGen.SourceDump property (in the same way you could see above) to the desired directory (note that environment variables are supported):
{
"configProperties": {
"ProxyGen.SourceDump": "%TEMP%"
}
}
In compile time (source generator):
Extend your .csproj with the following:
<PropertyGroup>
<ProxyGen_SourceDump>$(OutputPath)Logs</ProxyGen_SourceDump>
</PropertyGroup>
The output should look like this.
[Proxy|Duck]Generator.CacheDirectory is set somewhere)InterfaceInterceptor.Invoke() returns the result of the original method (instead of CALL_TARGET) so in the override you may never need to invoke the method parameter directly.[Proxy|Duck]Generator.GeneratedType[Async] property has been removed. To get the generated proxy type call the [Proxy|Duck]Generator.GetGeneratedType[Async]() method.[Proxy|Duck]Generator.CacheDirectory property has been removed. To set the cache directory tweak the runtimeconfig.json file.InterfaceInterceptor<>.Invoke() has been changed. Invocation parameters can be grabbed from the InvocationContext passed to the Invoke() method.ConcurrentInterfaceInterceptor<> class has been dropped since the InterfaceInterceptor<> class was rewritten in a thread safe manner.Generator.Activate() method.InvocationContext.InvokeTarget property has been removed but you should not be affected by itInterfaceInterceptor<TInterface>.Member|Method has been renamed to InterfaceMember|InterfaceMethodThis project currently targets .NET Standard 2.0 and 2.1.