ProxyGen.NET
9.1.0
dotnet add package ProxyGen.NET --version 9.1.0
NuGet\Install-Package ProxyGen.NET -Version 9.1.0
<PackageReference Include="ProxyGen.NET" Version="9.1.0" />
paket add ProxyGen.NET --version 9.1.0
#r "nuget: ProxyGen.NET, 9.1.0"
// Install ProxyGen.NET as a Cake Addin #addin nuget:?package=ProxyGen.NET&version=9.1.0 // Install ProxyGen.NET as a Cake Tool #tool nuget:?package=ProxyGen.NET&version=9.1.0
ProxyGen.NET
.NET proxy generator powered by Roslyn
This documentation refers the version 9.X of the library
Purposes
This library currently supports generating proxies for interface interception and duck typing.
To hook into interface method calls:
- Create the interceptor class (which is an InterfaceInterceptor descendant):
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);
}
}
- Generate a proxy instance invoking the desired constructor:
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()));
- Enjoy
Remarks:
- 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; } }
- Starting from v9.1 partial interface implementations are also supported:
using Solti.Utils.Proxy; public interface IMyInterface { void Intercepted(); void NotInterceptred(); } public class MyInterceptor: InterfaceInterceptor<IMyInterface> { public void NotInterceptred() {...} // will be triggered by Intercepted() only as NotInterceptred() has it's own implementation public override object Invoke(InvocationContext context) {...} ... }
For further usage examples see this or that.
To create ducks:
- Declare an interface that covers all the desired members of the target class:
public class TargetClass // does not implement IDuck
{
public void Foo() {...}
}
...
public interface IDuck
{
void Foo();
}
- Generate the duck instance:
using Solti.Utils.Proxy.Generators;
...
TargetClass target = ...;
IDuck duck = DuckGenerator<IDuck, TargetClass>.Activate(Tuple.Create(target)); // or ActivateAsync()
- Quack
Related tests can be seen here.
Caching the generated assembly
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"
}
}
Embedding the generated type
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:
- Open generics are not supported.
- coveralls.io (and other coverage reporters) may crash if your project was augmented by a source generator. To work this issue around:
- Ignore the generated sources in your coverage app (e.g.: in OpenCover use the
-filter:-[*]Proxies.GeneratedClass_*
switch) - Create an empty file for each generated class (e.g.:
YourProject\Solti.Utils.Proxy\Solti.Utils.Proxy.Internals.ProxyEmbedder\Proxies.GeneratedClass_XxX.cs
) - Exclude these files from your project:
<ItemGroup> <Compile Remove="Solti.Utils.Proxy\**" /> <EmbeddedResource Remove="Solti.Utils.Proxy\**" /> <None Remove="Solti.Utils.Proxy\**" /> </ItemGroup>
- Ignore the generated sources in your coverage app (e.g.: in OpenCover use the
Inspecting the generated code
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.
Migrating from version
- 2.X
- Delete all the cached assemblies (if the
[Proxy|Duck]Generator.CacheDirectory
is set somewhere) InterfaceInterceptor.Invoke()
returns the result of the original method (instead ofCALL_TARGET
) so in the override you may never need to invoke themethod
parameter directly.
- Delete all the cached assemblies (if the
- 3.X
[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.
- 4.X
- The layout of the
InterfaceInterceptor<>.Invoke()
has been changed. Invocation parameters can be grabbed from theInvocationContext
passed to theInvoke()
method. - The
ConcurrentInterfaceInterceptor<>
class has been dropped since theInterfaceInterceptor<>
class was rewritten in a thread safe manner.
- The layout of the
- 5.X
- You don't need to manually activate the generated proxy type, instead you may use the built-in
Generator.Activate()
method.
- You don't need to manually activate the generated proxy type, instead you may use the built-in
- 6.X
- The
InvocationContext.InvokeTarget
property has been removed but you should not be affected by it - As proxy embedder has been reimplemented using the v2 Source Generator API, this feature now requires VS 2022
- The
- 7.X
InterfaceInterceptor<TInterface>.Member|Method
has been renamed toInterfaceMember|InterfaceMethod
- 8.X
Generator
s have been demoted toclass
. To compareGenerator
instances use theirId
property.
Resources
Supported frameworks
This project currently targets netstandard2.0
as well as netstandard2.1
and had been tested against net472
, netcoreapp3.1
, net5.0
, net6.0
, net7.0
and net8.0
.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 4.3.1)
- Mono.Reflection (>= 2.0.0)
-
.NETStandard 2.1
- Microsoft.CodeAnalysis.CSharp (>= 4.3.1)
- Mono.Reflection (>= 2.0.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on ProxyGen.NET:
Package | Downloads |
---|---|
Injector.NET
A featherweight dependency injector. |
|
Solti.Utils.OrmLite.Extensions
OrmLite extensions |
|
RPC.NET.Client
Lightweight client to invoke RPC services built with RPC.NET |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
9.1.0 | 442 | 12/28/2023 |
9.1.0-RoslynV3 | 113 | 12/28/2023 |
9.0.0 | 231 | 12/9/2023 |
9.0.0-RoslynV3 | 108 | 12/9/2023 |
8.2.1 | 322 | 5/29/2023 |
8.2.1-RoslynV3 | 109 | 5/29/2023 |
8.2.0 | 430 | 3/12/2023 |
8.2.0-RoslynV3 | 124 | 3/12/2023 |
8.1.1 | 264 | 2/26/2023 |
8.1.0 | 550 | 2/12/2023 |
8.0.1 | 431 | 1/22/2023 |
8.0.0 | 546 | 12/21/2022 |
8.0.0-preview1 | 137 | 11/23/2022 |
7.1.0 | 425 | 11/5/2022 |
7.0.0 | 779 | 8/30/2022 |
7.0.0-preview6 | 319 | 6/4/2022 |
7.0.0-preview5 | 250 | 5/20/2022 |
7.0.0-preview4 | 201 | 5/11/2022 |
7.0.0-preview3 | 179 | 4/28/2022 |
7.0.0-preview2 | 167 | 4/26/2022 |
7.0.0-preview1 | 205 | 3/19/2022 |
6.0.1 | 1,252 | 3/16/2022 |
6.0.0 | 1,153 | 3/13/2022 |
6.0.0-preview2 | 161 | 3/13/2022 |
6.0.0-preview1 | 163 | 3/5/2022 |
5.0.1 | 2,354 | 12/12/2021 |
5.0.0 | 2,094 | 7/19/2021 |
4.0.2 | 1,734 | 4/14/2021 |
4.0.1 | 2,390 | 3/17/2021 |
4.0.0 | 939 | 3/13/2021 |
4.0.0-preview8 | 1,033 | 1/27/2021 |
4.0.0-preview7 | 297 | 1/24/2021 |
4.0.0-preview6 | 206 | 1/23/2021 |
4.0.0-preview5 | 238 | 1/22/2021 |
4.0.0-preview4 | 232 | 1/22/2021 |
4.0.0-preview3 | 254 | 1/17/2021 |
4.0.0-preview2 | 223 | 1/14/2021 |
4.0.0-preview1 | 343 | 11/17/2020 |
3.1.4 | 1,187 | 10/7/2020 |
3.1.3 | 1,284 | 9/25/2020 |
3.1.2 | 437 | 9/24/2020 |
3.1.1 | 712 | 9/8/2020 |
3.1.0 | 452 | 8/31/2020 |
3.0.3 | 515 | 8/27/2020 |
3.0.2 | 3,310 | 7/9/2020 |
3.0.1 | 435 | 7/6/2020 |
3.0.0 | 836 | 6/15/2020 |
2.1.1 | 766 | 6/2/2020 |
2.1.0 | 456 | 6/2/2020 |
2.0.3 | 1,034 | 5/21/2020 |
2.0.2 | 461 | 5/19/2020 |
2.0.1 | 433 | 5/19/2020 |
2.0.0 | 1,174 | 4/2/2020 |
1.1.1 | 1,848 | 1/31/2020 |
1.1.0 | 899 | 1/23/2020 |
1.0.0 | 675 | 1/18/2020 |
1.0.0-preview2 | 450 | 1/14/2020 |
1.0.0-preview1 | 341 | 1/8/2020 |