Click here to Skip to main content
15,884,099 members
Articles / Mobile Apps / Xamarin

Generating Proxies During Runtime using Reflection.Emit

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
8 Dec 2016CPOL15 min read 8.4K   66   3  
Reflection.Emit is very powerful tool. It creates IL code and since C# is converted into IL too, we have the same functionality as in C# and even more. It is very powerful and at the same time very complicated. Because of that, it is worth discussing how and for what it should be used.

One idea is to create dynamic code with automatic implementations of interfaces - proxy types.

Things You Can Do But Probably Will Not.

First thing we can try is to implement automatic calls to OnPropertyChanged in properties setters.

Let's start with a simple WPF application with a single window defined in XAML as follows:

XML
<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <TextBlock Text="{Binding Model.IntValue}"></TextBlock>
        </StackPanel>
        <StackPanel Grid.Row="1" 
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <Button Command="{Binding Increment}" 
            Content="Increment" Padding="10"></Button>
        </StackPanel>
    </Grid>
</Window>

View model for main window looks like this:

C#
using System.Windows.Input;

namespace TestApp
{
    public class MainWindowViewModel
    {
        private ICommand _increment;

        public MainWindowViewModel()
        {
            Model = new MainModel();
        }

        public ICommand Increment => _increment ?? (_increment = new Command(OnIncrement));

        public MainModel Model { get; set; }

        private void OnIncrement()
        {
            Model.Increment();
        }
    }
}

As you can see, we have a single text block and single button. View model has a single integer value and command that runs method on model.

Model looks like this:

C#
namespace TestApp
{
    public class MainModel
    {
        public int IntValue { get; set; }

        public void Increment()
        {
            IntValue++;
        }
    }
}

Ok. After button click, text block value should be incremented.

How to do that? Binding should listen to OnPropertyChange call of IPropertyChanged interface. But for a lot of properties, it is quite a bit of work to implement all of them manually. Instead, we can do it automatically with proxy created via Reflection.Emit.

C#
public static class ProxyGenerator
{
    public static T PropertyChangedProxy<T>() where T : class, new()
    {
        var type = typeof(T);
        var assemblyName = type.FullName + "_Proxy";
        var fileName = assemblyName + ".dll";
        var name = new AssemblyName(assemblyName);
        var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
        var module = assembly.DefineDynamicModule(assemblyName, fileName);
        var typeBuilder = module.DefineType(type.Name + "Proxy",
            TypeAttributes.Class | TypeAttributes.Public, type);
        typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
        var onPropertyChangedMethod = type.GetMethod("OnPropertyChanged",
            BindingFlags.Instance | BindingFlags.NonPublic);
        var propertyInfos = type.GetProperties().Where(p => p.CanRead && p.CanWrite);
        foreach (var item in propertyInfos)
        {
            var baseMethod = item.GetGetMethod();
            var getAccessor = typeBuilder.DefineMethod
                              (baseMethod.Name, baseMethod.Attributes, item.PropertyType, null);
            var il = getAccessor.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.EmitCall(OpCodes.Call, baseMethod, null);
            il.Emit(OpCodes.Ret);
            typeBuilder.DefineMethodOverride(getAccessor, baseMethod);
            baseMethod = item.GetSetMethod();
            var setAccessor = typeBuilder.DefineMethod
                   (baseMethod.Name, baseMethod.Attributes, typeof(void), new[] { item.PropertyType });
            il = setAccessor.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Call, baseMethod);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldstr, item.Name);
            il.Emit(OpCodes.Call, onPropertyChangedMethod);
            il.Emit(OpCodes.Ret);
            typeBuilder.DefineMethodOverride(setAccessor, baseMethod);
        }
        var t = typeBuilder.CreateType();
        assembly.Save(fileName);
        return Activator.CreateInstance(t) as T;
    }
}

Simple overwrite all properties of type and because of that, it works without problems.

Image 1

The disadvantage of this solution is, even if it can be easily binded in XAML, it can't be called easily from code. Those properties are not visible via base type since this type does not have virtual properties. Considering that type with overridden properties is dynamically created, it cannot be called from code (because it does not exist in compile time). Only way to call them from code is via Reflection mechanism.

It makes it much less elegant.

Things You Can Do But Probably Should Not

The disadvantage of Reflection.Emit is really poor documentation of how it is supposed to work and how it should be used. For example, I could not find a good example of how to define an event (I am talking about official Microsoft documentation). There is nothing about using TypeBuilder.DefineEvent method on MSDN. Good thing there is StackOverflow and a lot of blogs like this one. Chances are really high that someone tried the same thing before. Smile

Ok, going back to the subject. Previous implementation of proxy generation lacks automatic implementation of interface INotifyPropertyChanged. You have to do it yourself in every class you want to create proxy of (or use some kind of base class, but we are talking about a problem when this is impossible or not recommended).
Good thing it is possible to implement this interface dynamically in proxy using Reflection.Emit too. To do that, we need to create event PropertyChanged, which requires to:

  1. Declare event field
  2. Add event declaration
  3. Add Add accessor
  4. Add Remove accessor
  5. Define raise method

Quite a few things to do, since in C# you have to do 2 things to create event:

C#
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Define an event and raise method. Simple. In IL, things as you see above is are much more complicated.

Ok. To implement interface dynamically, we have to first add this interface to class. In Reflection.Emit, this requires only a single line, luckily. Smile

C#
typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));

After that, we can define event itself.

C#
var field = typeBuilder.DefineField("PropertyChanged", 
typeof(PropertyChangedEventHandler), FieldAttributes.Private);
var eventInfo = typeBuilder.DefineEvent("PropertyChanged", 
EventAttributes.None, typeof(PropertyChangedEventHandler));

As you can see, we are defining field to store delegates and event itself. Both members are taken care of in C# by event definition. Also, the same definition in C# creates two accessors: add and remove. We can of course create accessors ourselves but it is very rarely needed. In IL, we have to do it manually. How? First, we need to explain few things.

Events in C# are handled by Delegate types. Delegate class has static method Combine, which combines two delegates into invocation list. And this mechanism is used to store all handlers in a single value of an event field. When event is raised, all methods from invocation list are called. To be able to use Delegate.Combine, we have to retrieve this method via Reflection.

C#
var combine = typeof(Delegate).GetMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });

Now, we can create add accessor method and implement it.

C#
var ibaseMethod = typeof(INotifyPropertyChanged).GetMethod("add_PropertyChanged");
var addMethod = typeBuilder.DefineMethod("add_PropertyChanged",
    ibaseMethod.Attributes ^ MethodAttributes.Abstract,
    ibaseMethod.CallingConvention,
    ibaseMethod.ReturnType,
    new[] { typeof(PropertyChangedEventHandler) });
var generator = addMethod.GetILGenerator();
var combine = typeof(Delegate).GetMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, combine);
generator.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
eventInfo.SetAddOnMethod(addMethod);

Few things need clarification. First of all, we are implementing interface method (even if interface does not have this method declared explicitly) and we need this method metadata for implementation to work. After we get interface method info, we can use it to create override. Overridden methods have to have the same parameters, return type, calling conventions and attributes (without Abstract, because it is method with implementation).

There are only few IL codes in implementation, but it needs explanation anyway.

First, IL is not C# so you need to look at it a little differently. In C# method, you have state in form of set of temporary variables (if it is a pure method) and in class itself. In IL, you have only stack which is kind of a state of a IL method. Variables on stack determine source of fields, instance methods, and methods parameters. In the above example, what was most difficult to understand for me were the first two lines. Why there is a loading of first method argument two times to stack? Let's look at the picture below. First 'row' is a stack after execution of first two codes: Ldarg_0.

Image 4

We load source instance (C# this) two times which is always the first argument of a instance method (even if this is not visible in C#). After that, we have two items on stack, both are reference to the same object. After next operation (Ldfld) which takes only one argument (assembly operations usually take none or one argument), we also have two values on stack (source instance and value of field, which replaces second reference to source instance). Next operations (Ldarg_1) loads second argument of method on top a stack which causes stack to contains three values. Top two items from stack are passed to Delegate.Combine call, which replaces two items on top of stack with new combined delegate. Next IL code (Castclass) replaces item on top by casting it to appropriate type of event, which is then used to set field of source instance with new value of event handler (Stfld). Returned value is void so the rest of the stack is discarded and none value is returned from method (if method is non void, top item from stack is returned).

With added implementation of method add_PropertyChanged, most of this code can be reused for remove accessor, but instead of Delegate.Combine, we use Delegate.Remove.

C#
var ibaseMethod = typeof(INotifyPropertyChanged).GetMethod("remove_PropertyChanged");
var removeMethod = typeBuilder.DefineMethod("remove_PropertyChanged",
    ibaseMethod.Attributes ^ MethodAttributes.Abstract,
    ibaseMethod.CallingConvention,
    ibaseMethod.ReturnType,
    new[] { typeof(PropertyChangedEventHandler) });
var remove = typeof(Delegate).GetMethod("Remove", new[] { typeof(Delegate), typeof(Delegate) });
var generator = removeMethod.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, remove);
generator.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
eventInfo.SetRemoveOnMethod(removeMethod);

Much more interesting is implementation of raise method. C# implementation is pretty straightforward.

C#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

We check if event has any handlers and invoke all of them. In IL, things are much more complicated.

C#
var methodBuilder = typeBuilder.DefineMethod("OnPropertyChanged",
    MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig |
    MethodAttributes.NewSlot, typeof(void),
    new[] { typeof(string) });
var generator = methodBuilder.GetILGenerator();
var returnLabel = generator.DefineLabel();
var propertyArgsCtor = typeof(PropertyChangedEventArgs).GetConstructor(new[] { typeof(string) });
generator.DeclareLocal(typeof(PropertyChangedEventHandler));
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Brfalse, returnLabel);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Newobj, propertyArgsCtor);
generator.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke"));
generator.MarkLabel(returnLabel);
generator.Emit(OpCodes.Ret);
eventInfo.SetRaiseMethod(methodBuilder);
return methodBuilder;

Definition of this method (attributes) is taken from ILDASM application, used to inspect IL code generated from above C# code. I did not experiment with it nor I did I dwell on why it is written like that. After all, it is not a point and specialist and Microsoft knows what they are doing (at least I hope so Wink). My intention was to have IL code to work as much as C# event as it is possible.

New things among IL operation codes is label declaration. Thing is: in assemblers language, you do not have blocks, closures, functions, etc. You only have stream of operations and jumps for flow control. So instead of if statement, there is a label for return from method - if value of event handlers are null (no handlers for event), program jumps to end of method. If there are handlers, they are invoked and method ends.

Let's go through operations step by step.

First, of course, we load instance of object (which is the first argument with index of 0). After that, we load field from that instance represented by PropertyChanged field of event. After loading on to a stack, we store it in method variable (which is why DeclareLocal is called earlier). It is because we need this value twice: first to check if it is not null and second for calling it. Brfalse operation checks if value is boolean false. In C#, we have to check if value of a class is null or not null explicitly. In IL assembly language, the same operation checks if value is boolean false, integer 0 or null. If it is 'false', program control jumps to return label. If not, it continues and loads value of event again from local variable (first one was discarded by Brfalse operation). Next, we load instance of proxy MainModelProxy and name of changed property (in second parameter of method, at 1 index - Ldarg_1). Newobj operation calls given constructor. Since this constructor takes one parameter (name of property), this stack value is replaced by new object. So we have two items in stack: instance of MainModelProxy class and new instance of PropertyChangedEventArgs. With those values available, we can safely invoke Invoke method of PropertyChangedEventHandler (which is kind of static method since it does not take first argument as instance). This method does not return value so we can return from dynamic OnPropertyChanged method with Ret operation.

Ok. After removing implementation from MainModel class and running this code, we will see the same results as before: label text will show next value of integer after each button press, which proves that IL implementation of event works fine. Why is there is a problem then? After all, this section is titled things you 'should not do', right? Let's jump back to C# implementation in MainModel class and inspect details with ILDASM.exe. We will see the following IL code in add_PropertyChanged method.

ASM
.method public hidebysig newslot specialname virtual final 
        instance void  add_PropertyChanged
        (class [System]System.ComponentModel.PropertyChangedEventHandler 'value') cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = 
   ( 01 00 00 00 ) 
  // Code size       41 (0x29)
  .maxstack  3
  .locals init (class [System]System.ComponentModel.PropertyChangedEventHandler V_0,
           class [System]System.ComponentModel.PropertyChangedEventHandler V_1,
           class [System]System.ComponentModel.PropertyChangedEventHandler V_2)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [System]System.ComponentModel.PropertyChangedEventHandler 
                       TestApp.MainModel::PropertyChanged
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  ldarg.1
  IL_000b:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine
                       (class [mscorlib]System.Delegate,
                        class [mscorlib]System.Delegate)
  IL_0010:  castclass  [System]System.ComponentModel.PropertyChangedEventHandler
  IL_0015:  stloc.2
  IL_0016:  ldarg.0
  IL_0017:  ldflda     class [System]System.ComponentModel.PropertyChangedEventHandler 
                       TestApp.MainModel::PropertyChanged
  IL_001c:  ldloc.2
  IL_001d:  ldloc.1
  IL_001e:  call       !!0 [mscorlib]System.Threading.Interlocked::CompareExchange(!!0&,
                                                                                   !!0,
                                                                                   !!0)
  IL_0023:  stloc.0
  IL_0024:  ldloc.0
  IL_0025:  ldloc.1
  IL_0026:  bne.un.s   IL_0007
  IL_0028:  ret
} // end of method MainModel::add_PropertyChanged

As you can see, it is entirely different. There is a loop that checks for a value returned from System.Threading.Interlocked.CompareExchange method! What for? Probably because of thread safe code for changing of event handlers invocation list. And this is out of a box with new C# compiler. Since there really is no point in informing a C# users of every change of how C# compiler compiles code to IL, if you are interested, you have to inspect it yourself. It is a lot of work to keep your IL code in line with what Microsoft does. So even if you can write IL code that is supposed to do the same as IL code generated by C# compiler - you can't be sure unless you check it yourself. Add to that, a very troublesome process of creating such IL code (you can't debug it; you can't really tell what is wrong with it - you just will be greeted with ubiquitous 'IL code is not correct' error without details) - I strongly urge you to write IL code with Reflection.Emit that only does:

  1. Calls to other C# methods
  2. Casts objects to other types

Any other, more complicated code can be written in C# and then invoked via IL. It really does not matter if method is instance one (unless it uses state of object) or static one. For example, in my opinion, it is better to have a method that takes care of null-checking, creating instance of PropertyChangedEventArgs and calling PropertyChangeHandler. Consider the following static method.

C#
public static class PropertyChangedInvoker
{
    public static void Invoke(INotifyPropertyChanged sender,
        PropertyChangedEventHandler source, string propertyName)
    {
        source?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
    }
}

This static method in static class can be safely called from IL and in those all the logic that was previously implemented in IL inside OnPropertyChanged method! Thing is we have really easy to read and (most important!) debuggable, testable code. In IL, it is not possible. The only thing left to do in IL is to call this method with correct set of parameters.

ASM
var methodBuilder = typeBuilder.DefineMethod("OnPropertyChanged",
    MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig |
    MethodAttributes.NewSlot, CallingConventions.Standard | CallingConventions.HasThis, typeof(void),
    new[] { typeof(string) });
var generator = methodBuilder.GetILGenerator();
var returnLabel = generator.DefineLabel();
generator.DeclareLocal(typeof(PropertyChangedEventHandler));
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, typeof(PropertyChangedInvoker).GetMethod("Invoke"));
generator.MarkLabel(returnLabel);
generator.Emit(OpCodes.Ret);
eventInfo.SetRaiseMethod(methodBuilder);
return methodBuilder;

As you can see, in IL, we only collect set of parameters. Sender from this in first argument of a instance method OnPropertyChanged, event field value and property name for event arguments constructor.

On a side note, IMHO, it is funny that we can't directly invoke event outside of a class unless we access event field value first. Smile

Someone may say that this IL is not really that less complicated. However, if you will look closer, it is totally independent from changes of event definition, event arguments class definition and event delegate definition. If you will not change number of arguments needed by raise method and arguments class parameters (of course, there is a really small chance that those things change in PropertyChangeEventHandler since it is a .NET class) you are safe and sound.

Point from all of this is:

Use as little IL as possible, since working with it in VS is quite painful.

Things You Can Do and Probably Should Because It's Cool

I am currently working on mobile Xamarin applications. One of most missing features is WCF (and other services) proxies. It is possible to use third party libraries, but I could not find one that suits my needs 100%. Because of that I decided to write proxy generating mechanism myself. This is an ideal place for Reflection.Emit since you have almost the same code for every HTTP/HTTPS call with small differences like HTTP method (GET, POST, etc.) or return/parameter type(s).
Consider the following self-hosted JSON WCF service defined as follows:

C#
public class Service : IService
{
    public int Add(AddRequest req)
    {
        return req.FirstNumber + req.SecondNumber;
    }

    public string Test(string param)
    {
        return param;
    }
}

As you can see, we have two methods: Test (simple echo) and Add that sums two arguments. Nothing fancy, but we do not need anything more since it is enough to show use of Reflection.Emit in this scenario.
Contract is defined in interface IService.

C#
[ServiceContract]
public interface IService
{
    [OperationContract]
    [WebInvoke(Method = "POST",
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json)]
    int Add(AddRequest req);

    [OperationContract]
    [WebGet]
    string Test(string param);
}

First one uses POST HTTP method and transports messages using JSON. Second one: GET and simple strings.

Self hosting is done by simple code in the second test, console application.

C#
static void Main(string[] args)
{
    var serviceHost = new ServiceHost(typeof(Service), new Uri("http://localhost:8081"));
    using (serviceHost)
    {
        var seb = new WebHttpBehavior
        {
            DefaultOutgoingRequestFormat = WebMessageFormat.Json,
            DefaultOutgoingResponseFormat = WebMessageFormat.Json,
            FaultExceptionEnabled = true
        };
        serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
        var e = serviceHost.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "");
        e.Behaviors.Add(seb);
        serviceHost.Open();
        Console.WriteLine("Service ready...");
        Console.ReadLine();
        serviceHost.Close();
    }
}

Ok. This is really simple code. Almost minimal code to run self-hosted JSON service. How to call it from WPF application? HttpClient .NET class is more than enough.

C#
private void OnCallService()
{
    using (var client = new HttpClient(new HttpClientHandler()))
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var addRequest = new AddRequest
        {
            FirstNumber = First,
            SecondNumber = Second
        };
        var serializeObject = JsonConvert.SerializeObject(addRequest);
        var call = client.PostAsync("http://localhost:8081/add",
            new StringContent(serializeObject, Encoding.UTF8, "application/json"));
        try
        {
            call.Wait();
            var result = call.Result.Content;
            Sum = (int)JsonConvert.DeserializeObject(result.ReadAsStringAsync().Result, typeof(int));
        }
        catch (System.Exception)
        {
        }
    }
}

Simple using statement with HttpClient instance, configuring it for POST request with JSON string and deserializing result value from call, to appropriate type.

How can we automate this with Reflection.Emit and proxy generation? First of all, we need some type as base for our proxy. Luckily, WCF services have contracts and they are usually shared with client anyway. We can implement this interface! Smile

Ok, first of all, let's go back to ProxyGenerator class. We can add new method ServiceProxy to it for generating service proxies.

C#
public static T ServiceProxy<T>(string baseUrl) where T : class
{
    var serviceInterface = typeof(T);
    if (serviceInterface.IsInterface)
    {
        var assemblyName = serviceInterface.FullName + "_Proxy";
        var fileName = assemblyName + ".dll";
        var name = new AssemblyName(assemblyName);
        var assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
        var module = assembly.DefineDynamicModule(assemblyName, fileName);
        var implemntationName = serviceInterface.Name.StartsWith("I") ?
            serviceInterface.Name.Substring(1) : serviceInterface.Name;
        var typeBuilder = module.DefineType(implemntationName + "Proxy",
            TypeAttributes.Class | TypeAttributes.Public);
        typeBuilder.AddInterfaceImplementation(serviceInterface);
        foreach (var method in serviceInterface.GetMethods().Where(m => !m.IsSpecialName))
        {
            var customAttributes = method.GetCustomAttributes<OperationContractAttribute>()
                .SingleOrDefault();
            if (customAttributes != null)
            {
                var webInvokeAttr = method.GetCustomAttribute<WebInvokeAttribute>();
                var webGetAttr = method.GetCustomAttribute<WebGetAttribute>();
                ImplementServiceMethod(baseUrl, typeBuilder, method, webInvokeAttr, webGetAttr);
            }
            else
            {
                throw new Exception("Service interface has to be marked with correct method attribute!");
            }
        }
        var type = typeBuilder.CreateType();
        assembly.Save(assemblyName);
        return (T)Activator.CreateInstance(type);
    }
    return null;
}

First few lines is nothing new (already done in proxies for property changed event) nor is it interesting. Fun begins with adding service implementation to our new type. We of course need service interface in proxy type and that is why AddInterfaceImplementation method is used. Next thing to do is to search for all interface methods (that are not properties accessors; since all get_{property} and set_{property} methods have IsSpecialName set to true). In all methods, we search for WeGet or WebInvoke attribute. Those attributes are required in service methods since without it, we do not know what kind of url or HTTP method should be used. Core implementation of service method call is done by ImplementServiceMethod method.

C#
private static void ImplementServiceMethod(string baseUrl, TypeBuilder typeBuilder, MethodInfo method,
                                            WebInvokeAttribute webInvokeAttr, WebGetAttribute webGetAttr)
{
    var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
    var methodBuilder = typeBuilder.DefineMethod
                        (method.Name, method.Attributes ^ MethodAttributes.Abstract,
                        method.CallingConvention, method.ReturnType,
                        parameterTypes);
    var il = methodBuilder.GetILGenerator();
    var serviceCallMethod = typeof(ProxyGenerator).GetMethod("BaseServiceCall",
        BindingFlags.Public | BindingFlags.Static).MakeGenericMethod
                                (parameterTypes[0], method.ReturnType);

    var url = new Uri(new Uri(baseUrl), method.Name).AbsoluteUri;
    if (webGetAttr != null)
    {
        url = url + "?" + method.GetParameters()[0].Name + "=";
    }

    il.Emit(OpCodes.Ldstr, url);
    il.Emit(OpCodes.Ldstr, webGetAttr != null ? "GET" : "POST");
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Call, serviceCallMethod);
    il.Emit(OpCodes.Ret);
}

Core of the logic is handled by adding IL codes. But first, we need to define method override using the same parameters, return type, method attributes and calling conventions as interface method (again attributes are the same except for Abstract one, since this one marks only abstract methods and interface methods). Of course, for simplicity, we use only single parameter for implementation. If you are using DTO for services methods, they should only have one parameter (DTO class). Second, if you have more than one parameter, you certainly should use DTO class (it makes rewriting, refactoring much easier, which happens always, sooner or later); third if you have more than one custom DTO class method (there is really no point in doing that), you are probably doing something wrong.

Anyway, if your service method has more than one parameter, you should refactor it to use only one and if it is not possible, you can easily change collection of IL codes to load one (or more) extra parameter before calling BaseServiceCall method.

Then we change url if service method should be called with GET (because parameter should be serialized for query string). Of course, here is the place to implement url template (i.e., /customer/{id}), but this is a simple example, so call ToString on request object is enough to make this work. After that, we can actually create new method body. Since this is a proxy method and the entire work is done by BaseServiceCall, we just load three parameters (method url, HTTP method string, and parameter of service method) and then execute call to method defined as follows:

C#
public static TReturn BaseServiceCall<TParam, TReturn>(string url, string method, TParam param)
{
    using (var client = new HttpClient(new HttpClientHandler()))
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var serializeObject = JsonConvert.SerializeObject(param);
        var stringContent = new StringContent(serializeObject, Encoding.UTF8, "application/json");
        Task<HttpResponseMessage> call;
        if (method == "POST")
        {
            call = client.PostAsync(url, stringContent);
        }
        else
        {
            call = client.GetAsync(url + param);
        }
        var result = call.Result.Content;
        return (TReturn)JsonConvert.DeserializeObject
              (result.ReadAsStringAsync().Result, typeof(TReturn));
    }
}

With the above code, we can change our WPF application and test if this works. The below XAML should be added inside MainWindow.xaml file.

XML
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Vertical"
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
    <TextBlock Text="Service proxy test" FontWeight="Bold" />
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Grid.Row="0" Text="First arg" />
        <TextBlock Grid.Column="1" Grid.Row="0" Text="Second arg" />
        <TextBox Grid.Column="0"
                        Grid.Row="1" x:Name="First" 
                        Width="100" Text="{Binding First}" />
        <TextBox Grid.Column="1"
                        Grid.Row="1"
                        x:Name="Second" Width="100" Text="{Binding Second}" />
    </Grid>
    <TextBlock x:Name="Sum" Text="{Binding Sum}" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" Grid.Row="1"
                Grid.Column="1"
            VerticalAlignment="Center">
    <Button Command="{Binding CallService}" 
            Content="Call Add" Padding="10" />
</StackPanel>
<Grid Grid.Row="2" Grid.Column="1"
                HorizontalAlignment="Center"
            VerticalAlignment="Center">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"  />
        <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Grid.Column="0" 
    Text="Param:" />
    <TextBox Grid.Row="0" Grid.Column="1" 
    Text="{Binding EchoParam}" />
    <TextBlock Grid.Row="1" Grid.Column="0" 
    Text="Result:" />
    <TextBlock Grid.Row="1" Grid.Column="1" 
    Text="{Binding Echo}" />
    <Button Grid.Row="2" Grid.Column="0" 
    Grid.ColumnSpan="2" Content="Call Test" 
     Command="{Binding CallEcho}" />
</Grid>

Buttons binds to commands in view model handled by two functions:

C#
private void OnCallEcho()
{
    var serviceProxy = ProxyGenerator.ServiceProxy<IService>("http://localhost:8081");
    Echo = serviceProxy.Test(EchoParam);
}

private void OnCallService2()
{
    var serviceProxy = ProxyGenerator.ServiceProxy<IService>("http://localhost:8081");
    Sum = serviceProxy.Add(new AddRequest
    {
        FirstNumber = First,
        SecondNumber = Second,
    });
}

Very simple code. First, create proxy with interface and then call interface method. It can't be any simpler. Smile

After starting WPF application, we can try it out. After trying it out yourself, you should see similar results as in below GIF.

Image 9

As you can see, it works without a problem.

Of course, there is no need for new proxy every time we want to call service and since proxy does not have any state, it can be used as singleton instance. Also, there is no need to create new type every time proxy is created. Assembly is saved as .dll file on disk and can be very easily loaded to domain and proxy type can be reused.

Reflection.Emit is ideal for this scenario. We can create very easily proxy types from interface and simple manipulation of arguments, make base method do all heavy lifting. It is also really easy to maintain since you can change behavior of all methods in a single base method. For example, add logging, exception handling, alternative service server if first one will not respond, etc.

Example application can be downloaded from here or from Github.

If you want to try it out yourself, remember to start both projects (Service and TestApp) and run VS in administrator mode (it is required to start self-hosted WCF).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --