Git Product home page Git Product logo

sexy-proxy's Introduction

sexy-proxy

Async-enabled proxy generator with support for either compile-time or runtime based generation. By "proxy generator" we mean being able to take an existing class or interface and inject an interceptor that can generically handle each method in the source type and choose when and whether to pass the invocation off to the original implementation (if any). The idea is similar to Castle's DynamicProxy but in contrast to that library, sexy-proxy is fully async/await enabled. By that, we mean the methods around which you are proxying can return Task or Task<T> and your interceptor itself can use async and await without having to fuss with awkward uses of .ContinueWith.

Installation

Install using nuget:

Install-Package sexy-proxy

Getting Started

To get started we'll demonstrate a simple example using a virtual method in a base class. We will proxy that class and intercept the method, logging to a StringWriter (asynchronously!) both before and after proceeding to the original implementation. This sample will use the Reflection.Emit version because there are fewer dependencies and is thus simper for a first example.

To start, let's consider the following class:

public class HelloWorldPrinter
{
    public async virtual Task SayHello(TextWriter writer)
    {
        await writer.WriteAsync("Hello World!");
    }
}

Ordinarilly, you could use this method like so:

var writer = new StringWriter();
await new HelloWorldPrinter().SayHello(writer);

This would result in the string "Hello World!" being written to your StringWriter. But now let's use a proxy to change the data written to the writer such that the full string written is:

John says, "Hello World!"

HelloWorldPrinter can stay the same, but the callsite will change to:

1) var writer = new StringWriter();
2) var printer = Proxy.CreateProxy<HelloWorldPrinter>(async invocation =>
3) {
4)    await writer.WriteAsync("John says, \"");
5)    await invocation.Proceed();
6)    await writer.WriteAsync("\"");
7)    return null;
8) });
9) await printer.SayHello(writer);

Here we're storing off the printer in a separate variable (for clarity) and creating a proxy around HelloWorldPrinter with an invocation handler responsible for handling the method's interception. Let's break this down into smaller pieces:

  1. We create the proxy itself via Proxy.CreateProxy<HelloWorldPrinter>(...)
  2. The argument to CreateProxy expects a function that either returns an object or returns a Task<object>. The latter demonstrates async semantics, so we'll use that for our example.
  3. The argument passed to your function is an instance of Invocation (more details on that later) For now, suffice to say that it allows us to invoke the original behavior of the SayHello method at a time of our choosing.
  4. Line 4) asynchronously writes the string John says, " to the writer.
  5. Line 5) asynchronously calls the original implementation of SayHello, which writes the string Hello World! to the writer.
  6. Line 6) asynchronously closes the quotation by writing " to the writer.
  7. Finally, we invoke SayHello as before on line 9).

The end result of this is the string John says, "Hello World!" written to the writer as we expected.

Overview

At a high-level there are a handful of concepts that you should understand to take full advantage of this library.

  • Generator Types
  • The Invocation object passed to your handler
  • Proxies around unimplemented methods vs. those with existing behavior.
  • Target-based proxies
  • In-place proxies (requires the Fody version of the generator described below)

Generator Types

sexy-proxy provides two key ways of generating the proxy. One uses Reflection.Emit and is simple and works out of the box. (it has no dependencies) However, it has two key limitations:

  • It requires Reflection.Emit -- this is a problem for iOS apps.
  • It requires that proxy'd methods be virtual or that the proxy type be an interface (and that the type be public).

Furthermore, any costs associated with generating a proxy (admitedly fairly minimal) are incurrred at runtime rather than compile time.

If any of these concerns are paramount, sexy-proxy provides an alternative generator that uses Fody. This is a tool that allows the proxies to be generated at compile-time. This addresses all the aforementioned issues. The downsides of using Fody are minimal:

  • The generation happens at compile-time, so any penalty incurred for generating proxies must be dealt with every time you compile. So, for example, it can (very slightly) slow down your iterations when leveraging TDD.
  • On your proxy'd type you must either:
    • Decorate it with the [Proxy] attribute
    • Implement the interface IProxy

Generally speaking, we'd recommend using the Fody implementation since the costs are negligible and the benefits are useful. For example, you can proxy on any method, regardless of whether it's marked as virtual or private and the declaring type itself can be private or internal, which is not possible with Reflection.Emit.

The Invocation object passed to your handler

Your invocation handler is what is responsible for defining what happens when your methods are intercepted. The value returned by your invocation handler is what will be returned to the caller of the intercepted method. If the method's return type is void, then you must still return null (since the invocation handler has a non-void return type).

In scenarios in which you have an existing implementation you'd like to invoke (as in our Getting Started example where we still wanted to enlist the default implementation that wrote Hello World! to the writer), there is a method on the Invocation object called Proceed:

public abstract Task<object> Proceed();

There are two important points about this method signature:

  • It returns a Task<object> which means you may await it. And since your invocation handler can also be an async method (or lambda) this is straightforward.
  • It takes no arguments. This is because the invocation handler is the same for all the methods you may be intercepting, which could have wildly different arity.

Thus, the arguments to your intercepted method are contained in the Arguments property:

public object[] Arguments { get; set; }

Naturally, if your method has three parameters, then this array will contain three elements in parameter order. Importantly, you may modify this array to dynamically change the arguments that will be passed on to the original implementation if and when you invoke Proceed.

Proxies around unimplemented methods vs. those with existing behavior.

As in the example illustrated in Getting Started, you can intercept methods that already have existing behavior. If doing so, Proceed will allow you to invoke that behavior. In contrast, if the intercepted method is abstract or declared in an interface, then Proceed will do nothing but return the default value for the return type, or null if the return type is void.

Target-based proxies

Sometimes you already have an existing instance of your proxy type and you want to modify the behavior via the decorator pattern. Normally the decorator pattern requires you to manually override/implement the relevant methods, which of course is often exactly the right approach.

Other times, however, the behavior you want to apply can be generalized such that you'd rather not have to override each method individually. (for example, logging how long it took to invoke each method) In such a scenario, you can use sexy-proxy to create a proxy where the Proceed method will actually invoke the method on an arbitrary instance of the type that you provide when instantiating your proxy.

sexy-proxy's People

Contributors

josephdwyer avatar kirkplangrid avatar kswoll avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

sexy-proxy's Issues

License

Can you add a license.md so that we can use this in commercial projects? What is the license the library is released under and is there a perma link to that?

Unable to proxy class with generic method

Following example doesn't work:

public class SexyProxyTest
{
    public void GenericMethod<TModel>(TModel model)
    {
    }
}

public class SexyProxyTests
{
    [Test]
    public void Test()
    {
        var proxy = Proxy.CreateProxy<SexyProxyTest>(new SexyProxyTest(), x => new object());
    }
}

Sexy proxy throws folowing exception:

System.TypeInitializationException : The type initializer for 'RidgeTests.SexyProxyTest$Proxy' threw an exception.
  ----> System.BadImageFormatException : An attempt was made to load a program with an incorrect format. (0x8007000B)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at SexyProxy.Proxy`1.CreateProxy(T target, InvocationHandler invocationHandler)
   at SexyProxy.Proxy`1.CreateProxy(T target, Func`2 invocationHandler, ProxyPredicate`1 predicate)
   at SexyProxy.Proxy.CreateProxy[T](T target, Func`2 invocationHandler, ProxyPredicate`1 predicate)
   at RidgeTests.SexyProxyTests.Test() in C:\Users\michal.motycka\Desktop\Personal\Ridge\Tests\RidgeTests\SexyProxyTests.cs:line 39
--BadImageFormatException
   at RidgeTests.SexyProxyTest$Proxy..cctor()
``

Provide PropertyInfo to Invocation

Provide the PropertyInfo object to the invocation handler when the method is an accessor. This would avoid having to ever use StartsWith("set_") to determine if the method represents a setter, for example, which is kind of slow.

System.InvalidOperationException: 'It is illegal to reflect on the custom attributes of a Type loaded via ReflectionOnlyGetType (see Assembly.ReflectionOnly) -- use CustomAttributeData instead.'

Version: SexyProxy v2.0.113

Hi Kirk, our application uses SyntaxEditor from Actipro to provide intellisense and syntax highlighting of C# code in our application. This library uses ReflectionOnly calls to reflect on the types within any imported assemblies. When it attempts to load System.Web.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, we get the following exception:

System.InvalidOperationException: 'It is illegal to reflect on the custom attributes of a Type loaded via ReflectionOnlyGetType (see Assembly.ReflectionOnly) -- use CustomAttributeData instead.'
at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)
at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeAssembly assembly, RuntimeType caType)
at System.Attribute.GetCustomAttributes(Assembly element, Type attributeType, Boolean inherit)
at System.Reflection.CustomAttributeExtensions.GetCustomAttributes[T](Assembly element)
at SexyProxy.FodyProxyTypeFactory.<>c.<.cctor>b__1_0(Assembly assembly) in C:\projects\sexy-proxy\SexyProxy\FodyProxyTypeFactory.cs:line 17

Would it be possible to:

  1. Opt out of Getting ProxyForAttribute from every loaded assembly. I don't see that we use this attributed anywhere in our code so it would be nice to skip this call altogether.

  2. Or, use GetCustomAttributesData instead

  3. Or, Ignore assemblies loaded with ReflectionOnly context

         AppDomain.CurrentDomain.AssemblyLoad += (sender, args) =>
         {
             if(!args.LoadedAssembly.ReflectionOnly)
               checkAssembly(args.LoadedAssembly);
         };
    

Working with ref parameters

Hi! I tried to use sexy-proxy in my project, but got a problem with methods with ref parameters. In that case an exception is thrown here:
il.Emit(OpCodes.Ldtoken, type)
(EmitExtensions.cs line 32). ArgumentException (name represents a ByRef type).
Do you have plans to add a support of proxing classes having methods with ref parameters? Or does such an opportunity already exist?

Support for non-generic proxy creation

I am creating proxies for all types matching a predicate. Proxy.CreateProxy<T>() obviously can't be called as types aren't known up-front, but there doesn't appear to be an equivalent proxy creation method which accepts a Type argument.

Instead, I'm having to use the following bit of remarkably ugly reflection to invoke this method in a non-generic fashion:

var method = typeof(Proxy).GetMethods().SingleOrDefault(
        x => x.Name == nameof(Proxy.CreateProxy) &&
                x.IsGenericMethod &&
                x.GetParameters().Length == 3 &&
                x.GetParameters()[0].ParameterType == typeof(Func<Invocation, Task<object>>) &&
                x.GetParameters()[1].ParameterType.IsGenericType &&
                x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(ProxyPredicate<>) &&
                x.GetParameters()[2].ParameterType == typeof(AsyncInvocationMode));

    // Verify that the method exists
    if (method == null) throw new InvalidOperationException($"Unable to identify suitable proxy method on {typeof(Proxy).FullName}");

    // Create proxy
    return method
        .MakeGenericMethod(type)
        .Invoke(null, new object[]
        {
            new Func<Invocation, Task<object>>(async invocation => await ProxyService(type, provider, invocation)), null, AsyncInvocationMode.Throw
        });

Two questions:

  1. Is there a way to create a proxy by Type which I just haven't spotted?
  2. If not, is this something you could consider?

Could not find assembly: SexyProxy (netstandard, mscorlib)

Hello,

I created new project (netstandard 2.0 class library), and when I try to compile I get error from Fody: Fody/SexyProxy: Could not find assembly: SexyProxy (netstandard, mscorlib). I tried older version too, and get the same result.

Fody: 6.0.0.
SexyProxy: 2.0.110

Use a current version of Fody

We are using SexyProxy in our large project, and I am trying to upgrade some other packages that need to use Fody 5.0.6; however SexyProxy is using an older version which is causing some trouble.

P.S. could you consider renaming this lewd project, in my corporate job my manager many not like this?

Example usage with autofac

Hello again :-) [As a recap for anyone seeing this ticket for the first time]

This looks fantastic! A bit of a hidden gem when looking for async support for interceptors.

It would be great to see an example / extensions for autofac & interceptors so mere mortals like myself can make good use of it. :-)

Autofac has an extension package which sits onto of Dynamic Proxy to create AOP style interceptors.

This is probably a better explanation than I can give:

http://docs.autofac.org/en/latest/advanced/interceptors.html

And here is the code in the extension method that works the magic:

https://github.com/autofac/Autofac.Extras.DynamicProxy/blob/ff1822d4ffa8893dd1eac1bb674d22ccba68737d/src/Autofac.Extras.DynamicProxy/RegistrationExtensions.cs

It's a similar to ninject's extension in many ways.

Unable to proxy interface with generics

I may be missing something, but the following class can't be used to create a non-Fody proxy:

public interface IPoison
{
    void Foo<T>();
}

When trying to create a proxy using:

Proxy.CreateProxy<IPoison>(async invocation =>
{
    await invocation.Proceed();
    return null;
});

I receive:

System.TypeInitializationException : The type initializer for 'SexyProxy.Proxy`1' threw an exception.
  ----> System.TypeLoadException : Method 'Foo' in type 'Test.IPoison$Proxy' from assembly 'Test.IPoison$Proxy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
   at SexyProxy.Proxy`1.CreateProxy(T target, Func`2 invocationHandler, ProxyPredicate`1 predicate, AsyncInvocationMode asyncMode)

Unable to proxy class with out argument

Creating a proxy against a class with an out argument in any method fails with the following exception:

System.TypeInitializationException : The type initializer for 'SexyProxy.Proxy`1' threw an exception.
  ----> System.ArgumentException : Cannot get TypeToken for a ByRef type.

Is this a bug in sexy-proxy, or just a limitation of emitting code?

Example:

Proxy.CreateProxy<TestClass>(async invocation => await invocation.Proceed());
public class TestClass
{
    void Boom(object key, out string value) => value = null;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.