48

I'm working with the namespaces System.Runtime.Remoting.Proxies and System.Runtime.Remoting.Messaging for AOP in C#. I'm trying to port my application from .Net Framework 4.6 to dnxcore/dotnet core.

Intellisense says, that these two namespaces are not available with my framework-vesion (netcoreapp1.0 / dnxcore50). Any idea if these two namespaces will appear? or any idea how to get the AOP like with the RealProxy-class?

I don't want to use 3rd-party-libraries - I only want to use what .Net offers me.

Matthias Burger
  • 5,549
  • 7
  • 49
  • 94
  • did you find the solution to this??? – Rafael Enriquez Sep 12 '16 at 21:01
  • 1
    @RafaelEnriquez untill today there is none implemented in asp net core 1. Maybe with 1.1 or 1.2. when I found something I'll post the answere here :) – Matthias Burger Sep 13 '16 at 03:03
  • So what is the solution to it? Is https://www.nuget.org/packages/System.Runtime/ compatible with .NetCore? – Haseeb Jadoon May 19 '17 at 07:01
  • @HaseebJadoon I didn't test with the current version of netstandard/netcore/whatevernet - i wrote my own implementation for AOP. This post soon has his first birthday - but feel free to test with the current version and let us know whether it works or not :) – Matthias Burger May 19 '17 at 07:30
  • 1
    @MatthiasBurger i'm porting the spring.net to .net standard. Today di/ioc/aop are working. Some details you will see at https://github.com/spring-projects/spring-net/issues/133 – Luiz Carlos Faria Sep 28 '17 at 07:56

3 Answers3

51

It looks like RealProxy won't come to .NET Core/Standard. In the issue, a Microsoft developer suggests DispatchProxy as an alternative.

Also, some existing AOP frameworks may support .NET Core already or in the future (as seen in the comments on the question).

An alternative is the DispatchProxy, which has a wonderful example here: http://www.c-sharpcorner.com/article/aspect-oriented-programming-in-c-sharp-using-dispatchproxy/.

If we simplify the code, this is what we get:

public class LoggingDecorator<T> : DispatchProxy
{
    private T _decorated;

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        try
        {
            LogBefore(targetMethod, args);

            var result = targetMethod.Invoke(_decorated, args);

            LogAfter(targetMethod, args, result);
            return result;
        }
        catch (Exception ex) when (ex is TargetInvocationException)
        {
            LogException(ex.InnerException ?? ex, targetMethod);
            throw ex.InnerException ?? ex;
        }
    }

    public static T Create(T decorated)
    {
        object proxy = Create<T, LoggingDecorator<T>>();
        ((LoggingDecorator<T>)proxy).SetParameters(decorated);

        return (T)proxy;
    }

    private void SetParameters(T decorated)
    {
        if (decorated == null)
        {
            throw new ArgumentNullException(nameof(decorated));
        }
        _decorated = decorated;
    }

    private void LogException(Exception exception, MethodInfo methodInfo = null)
    {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} threw exception:\n{exception}");
    }

    private void LogAfter(MethodInfo methodInfo, object[] args, object result)
    {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} executed, Output: {result}");
    }

    private void LogBefore(MethodInfo methodInfo, object[] args)
    {
        Console.WriteLine($"Class {_decorated.GetType().FullName}, Method {methodInfo.Name} is executing");
    }
}

So if we have an example class Calculator with a corresponding interface (not shown here):

public class Calculator : ICalculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

we can simply use it like this

static void Main(string[] args)
{
    var decoratedCalculator = LoggingDecorator<ICalculator>.Create(new Calculator());
    decoratedCalculator.Add(3, 5);
    Console.ReadKey();
}
Manuel Allenspach
  • 12,467
  • 14
  • 54
  • 76
  • 1
    So `DispatchProxy` is the way to go now. Thanks for sharing your knowledge. :) – Matthias Burger Jan 12 '18 at 12:54
  • Just commenting that targetMethod.Invoke uses reflection and is slow. Also it doesn't work with async method without jumping through some hoops that involve even more slower reflections. The method works, but really isn't too ideal tbh. – Sleeper Smith Jan 02 '20 at 06:27
  • Sad. `DispatchProxy` can only work with interfaces but not with classes. – ADM-IT May 12 '21 at 17:57
13

You can use either System.Reflection.DispatchProxy or your own simple decorator implementations. Check Decorator pattern page on Wikipedia for implementation examples.

Currently in .NET Core, you can't use constructor injection with DispatchProxy. You have to use DispatchProxy.Create() factory method and property injection with explicit cast to proxy type that you want to use. For more information check DispachProxyTest.cs in .NET Core GitHub repository.

This is an example of a simple generic decorator that inherits DispatchProxy:

class GenericDecorator : DispatchProxy
{
    public object Wrapped { get; set; }
    public Action<MethodInfo, object[]> Start { get; set; }
    public Action<MethodInfo, object[], object> End { get; set; }
    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        Start?.Invoke(targetMethod, args);
        object result = targetMethod.Invoke(Wrapped, args);
        End?.Invoke(targetMethod, args, result);
        return result;
    }
}

And its usage:

class Program
{
    static void Main(string[] args)
    {
        IEcho toWrap = new EchoImpl();
        IEcho decorator = DispatchProxy.Create<IEcho, GenericDecorator>();
        ((GenericDecorator)decorator).Wrapped = toWrap;
        ((GenericDecorator)decorator).Start = (tm, a) => Console.WriteLine($"{tm.Name}({string.Join(',', a)}) is started");
        ((GenericDecorator)decorator).End = (tm, a, r) => Console.WriteLine($"{tm.Name}({string.Join(',', a)}) is ended with result {r}");
        string result = decorator.Echo("Hello");
    }

    class EchoImpl : IEcho
    {
        public string Echo(string message) => message;
    }

    interface IEcho
    {
        string Echo(string message);
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Leonid Vasilev
  • 11,910
  • 4
  • 36
  • 50
1

You can use Castle.Core which supports .Net Core:

using Castle.DynamicProxy;
using System;
using System.Reflection;

namespace ObservableProxyTest
{
    class Program
    {
        private static readonly ProxyGenerator Generator = new ProxyGenerator();

        static void Main(string[] args)
        {
            var obj = new MyEntity()
            {
                Name = "My name",
                Description = "My description"
            };

            var proxy = Generator.CreateClassProxyWithTarget(obj, new ObservableInterceptor());

            Console.WriteLine("Object changed: " + proxy.IsChanged);

            proxy.Name = "My name 2";
            proxy.Description = "My description 2";

            Console.WriteLine("Object changed: " + proxy.IsChanged);

            Console.ReadLine();
        }
    }

    internal interface IObservable
    {
        bool IsChanged { get; }
        void SetChanged();
    }

    public abstract class BaseEntity : IObservable
    {
        public virtual bool IsChanged { get; protected set; }

        public void SetChanged()
        {
            IsChanged = true;
        }
    }

    public class MyEntity : BaseEntity
    {
        // Virtual keyword is very important
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }
    }

    internal class ObservableInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var observable = invocation.InvocationTarget as IObservable;
            if (observable != null && !observable.IsChanged && IsSetter(invocation.Method))
            {
                observable.SetChanged();
            }
            invocation.Proceed();
        }

        private bool IsSetter(MethodInfo method)
        {
            return method.IsSpecialName && method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase);
        }
    }

}

Please also read very nice article: https://fullboarllc.com/change-tracking-structuremap-dynamicproxy/

ADM-IT
  • 3,719
  • 1
  • 25
  • 26