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 to discuss 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 simple WPF application with single window defined in XAML as follows.
<Window x_Class="TestApp.MainWindow" 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.
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 single text block and single button. View model have single integer value and command that runs method on model.
Model looks like this.
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 bit of work to implement all of them manually. Instead, we can do it automatically with proxy created via Reflection.Emit.
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 of all properties of type and because of that it works without problems.
Minus 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 do not have virtual properties. Considering that type with overridden properties is dynamically created it cannot be called from code (because it do not exists 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
Minus of Reflection.Emit is really poor documentation of how it supposed to work and how it should be used. For example I could not find 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. π
Ok, going back to 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:
- Declare event field
- Add event declaration
- Add Add accessor
- Add Remove accessor
- Define raise method.
Quite few things to do, since in C# you have to do 2 things to create event
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 single line, luckily. π
typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
After that we can define event itself.
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 creates 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 have static method Combine, which combines two delegates into invocation list. And this mechanism is used to store all handlers in single value of a 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.
var combine = typeof(Delegate).GetMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });
Now we can create add acessor method and implement it.
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 needs clarification. First off all, we are implementing interface method (even if interface do 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 method 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 a 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 above example what was most difficult to understand for me were first two lines. Why there is a loading of first method argument two times to stack? Lets look at picture below. First ‘row’ is a stack after execution of first two codes: Ldarg_0.
We load source instance (C# this) two times which is always 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 takes 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 whole rest of 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.
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
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
We check if event have any handlers and invoke all of them. In IL things are much more complicated.
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 experimented with it nor I dwelled why it is written like that. After all it is not a point and specialist and Microsoft know what they are doing (at least I hope so :)). 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.
Lets go through operations step by step.
First, of course we load instance of object (which is first argument with index of 0). After that we load field of from that instance represented by PropertyChanged field of event. After loading on to of 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 check 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 do not takes first argument as instance). This method do not returns 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 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 following IL code in add_PropertyChanged method.
.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 is no really a 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 do. So even if you can write IL code that suppose 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 do:
- Calls to other C# methods.
- Casts objects to other types.
Any other, more complicated code can be written in C# and then invoked via IL. It really do 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 method that takes care of null-checking, creating instance of PropertyChangedEventArgs andΒ calling PropertyChangeHandler. Consider following static method.
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 it 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.
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 the side note IMHO it is funny that we can’t directly invoke event outside of a class unless we access event field value first π
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 really small chance that those things change in PropertyChangeEventHandler since it is .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 could suits my needs 100%. Because of that I decided to write proxy generating mechanism myself. This is 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 following self-hosted JSON WCF service defined as follow.
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.
[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 second test, console application.
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.
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 we can 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! π
Ok, first of all lets go back to ProxyGenerator class. We can add new method ServiceProxy to it for generating service proxies.
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 it is 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.
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 probably doing something wrong.
Anyway if your service method have 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 simple example so callΒ ToString on request object is enough to make this works. After that we can actually create new method body. Since this is proxy method and whole 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 follow.
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 above code we can change our WPF application and test if this works. Below Xaml should be added inside MainWindow.xaml file.
<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.
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. π
After starting WPF application we can try it out. After trying it out yourself you should see similar results as in below GIF.
As you can see it works without a problems.
Of course there is no need for new proxy every time we want to call service and since proxy do 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 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 Generator-master.zip (22.62 kb) 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).