Skip to content

Instantly share code, notes, and snippets.

@miguelludert
Last active September 29, 2016 21:26
Show Gist options
  • Save miguelludert/39b8e115e29112297145 to your computer and use it in GitHub Desktop.
Save miguelludert/39b8e115e29112297145 to your computer and use it in GitHub Desktop.
Service Agent Factory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.ServiceModel;
using System.Threading;
using System.ServiceModel.Description;
using System.Diagnostics;
using System.Configuration;
using System.Collections.Specialized;
namespace ThinkingSites.Utilities
{
/// <summary>
/// Represents a static class that dynamically creates and exposes services based off of service contracts.
/// </summary>
public static class ServiceAgentFactory
{
private static object _Lock = new object();
/// <summary>
/// Calls a service dynamically based on a service contract, a method name, and an array of parameters.
/// </summary>
/// <typeparam name="TServiceContract">The type of the service contract</typeparam>
/// <param name="methodName">The method name of the service method to invoke.</param>
/// <param name="parameters">An array of parameters to pass to the method.</param>
/// <returns>The result of the invocation</returns>
/// <remarks>
/// Internally, it gets a service, via the GetService&lt;&gt; Method, dynamically selects the method and invokes it.
/// </remarks>
/// <example>
/// var serviceResults = ServiceAgentFactory.GetService&lt;I_ServiceContract&gt;().ServiceMethod();
/// </example>
[DebuggerStepThrough]
public static object CallServiceDynamicallyByName<TServiceContract>(string methodName, object[] parameters)
{
var serviceContract = GetService<TServiceContract>();
var method = typeof(TServiceContract).GetMethod(methodName);
if (method == null)
throw new InvalidOperationException(string.Format("Method {0} does not exist in contract {1}.",methodName,typeof(TServiceContract).Name));
return method.Invoke(serviceContract, parameters);
}
/// <summary>
/// Dynamically checks to see that a service within a service contract exists by name.
/// </summary>
/// <typeparam name="TServiceContract">The service contract to invoke</typeparam>
/// <param name="methodName"></param>
/// <returns></returns>
[DebuggerStepThrough]
public static bool CheckServiceNameExists<TServiceContract>(string methodName)
{
var method = typeof(TServiceContract).GetMethod(methodName);
return method != null;
}
/// <summary>
/// Gets a service based on a service contract.
/// </summary>
/// <typeparam name="TServiceContract">a service contract.</typeparam>
/// <returns>An implementation of the service contract.</returns>
[DebuggerStepThrough]
public static TServiceContract GetService<TServiceContract>()
{
// ensure that the _AgentTypes dictionary exists
if (_AgentTypes == null)
_AgentTypes = new Dictionary<Type, Type>();
if (_AgentDelegates == null)
_AgentDelegates = new Dictionary<Type, Delegate>();
var serviceContractType = typeof(TServiceContract);
// dynamically build the agent, instantiate it and return it.
object result;
if (_AgentDelegates.ContainsKey(serviceContractType))
result = _AgentDelegates[serviceContractType].DynamicInvoke();
else
{
GenerateImplementationType(serviceContractType, null);
result = Activator.CreateInstance(_AgentTypes[serviceContractType]);
}
return (TServiceContract)result;
}
/// <summary>
/// Gets or sets the list of usable agents
/// </summary>
private static Dictionary<Type, Type> _AgentTypes { get; set; }
/// <summary>
/// Get or sets agent delegates
/// </summary>
private static Dictionary<Type, Delegate> _AgentDelegates { get; set; }
/// <summary>
/// Does the actual work of generating an agent
/// </summary>
/// <param name="serviceContractType"></param>
/// <param name="implementationType"></param>
[DebuggerStepThrough]
private static void GenerateImplementationType(Type serviceContractType, Type implementationType)
{
// if the service contract does not exist in the _AgentTypes it's not been created yet. Create it now.
if (!_AgentTypes.ContainsKey(serviceContractType)) // don't enter a lock situation unless necassary
{
lock (_Lock)
{
if (!_AgentTypes.ContainsKey(serviceContractType)) // double check that the type does not already
{
// Put together the dynamic assembly and give it only run permissions.
// This dynamic assembly will be recreated once on first invocation after the application restarts.
var assemblyNameString = "Assembly_" + Guid.NewGuid().ToString();
AssemblyName assemblyName = new AssemblyName() { Name = assemblyNameString };
var domain = Thread.GetDomain();
AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
SetDebuggingSymbols(assemblyBuilder);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyNameString, true);
if(implementationType == null)
implementationType = CreateClientBase(moduleBuilder, serviceContractType); // create the ClientBase from the service contract
var agentType = CreateAgent(moduleBuilder, serviceContractType, implementationType); // create the wrapper complete with close/abort statements
_AgentTypes.Add(serviceContractType, agentType); // add agent to the dictionary
}
}
}
}
[DebuggerStepThrough]
[Conditional("DEBUG")]
private static void SetDebuggingSymbols(AssemblyBuilder assemblyBuilder)
{
var debuggableConstructor = typeof(DebuggableAttribute).GetConstructor(new[] { typeof(DebuggableAttribute.DebuggingModes) });
assemblyBuilder.SetCustomAttribute(new CustomAttributeBuilder(debuggableConstructor,new object[] { DebuggableAttribute.DebuggingModes.DisableOptimizations }));
}
/// <summary>
/// Dynamically creates a ClientBase from an service contract
/// </summary>
/// <param name="moduleBuilder">The module builder from the assembly in which it is to be created.</param>
/// <param name="serviceContract">The service contract to create.</param>
/// <returns>The dynamic type for the client base.</returns>
/// <remarks>
/// This method generates MSIL for a ClientBase that inherits the service contract. Effectively, each loop creates a method that looks like this:
///
/// public ReturnType MethodCall(ParameterType1 parm1,ParameterType2 parm2, ParameterType3 parm3)
/// {
/// base.InnerChannel.MethodCall(parm1,parm2,parm3);
/// }
///
/// </remarks>
[DebuggerStepThrough]
private static Type CreateClientBase(ModuleBuilder moduleBuilder, Type serviceContract)
{
var constructorSignature = new[] { typeof(string) };
var genericClientBase = typeof(ClientBase<>).MakeGenericType(serviceContract); // add a generic parameter.
TypeBuilder typeBuilder = moduleBuilder.DefineType(
serviceContract.Name + "_ClientImplementation",
TypeAttributes.Public | TypeAttributes.Class,
genericClientBase,
new[] { serviceContract }); // create a type based on the generic parameter and the service contract.
// creates a constructor for the client base and inherits the clientbase(string) constructor
var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Any, constructorSignature);
var ilConstructor = constructor.GetILGenerator();
var baseConstructor = genericClientBase.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
constructorSignature,
null);
ilConstructor.Emit(OpCodes.Ldarg_0); // loads "this" to the top of the stack
ilConstructor.Emit(OpCodes.Ldarg_1); // loads the first parameter onto the stack for the base constructor
ilConstructor.Emit(OpCodes.Call, baseConstructor); // calls the base constructor
ilConstructor.Emit(OpCodes.Ret); // returns the object created by the base consructor and leaves the method
// for each method in the service contract, create a call to the ClientBase's inner channel.
foreach (var iMethod in serviceContract.GetMethods())
{
var methodParameters = iMethod.GetParameters().Select(s => s.ParameterType).ToArray(); // get method parameters
var method = typeBuilder.DefineMethod(iMethod.Name, // create method signature.
MethodAttributes.Public | MethodAttributes.Virtual,
iMethod.ReturnType,
methodParameters);
var getChannelMethod = genericClientBase.GetProperty("InnerChannel").GetGetMethod(); // get the inner channel
var implementationMethod = serviceContract.GetMethod(iMethod.Name, methodParameters); // get the method from the service contract
var il = method.GetILGenerator(); // begin MSIL generation
il.Emit(OpCodes.Ldarg_0); // load 'this'
il.Emit(OpCodes.Call, getChannelMethod); // call the 'get' accessor for the inner channel and load the channel into memory
for (int i = 1; i <= methodParameters.Count(); i++) // load each method parameter so we can pass them into the implementation method
il.Emit(OpCodes.Ldarg, i);
il.Emit(OpCodes.Callvirt, implementationMethod); // invoke the implementation method
il.Emit(OpCodes.Ret); // return
}
return typeBuilder.CreateType();
}
/// <summary>
/// Creates a dynamic agent that will instantiate the appropriate ClientBase, invoke the method, and dispose of the remaining resources.
/// </summary>
/// <param name="moduleBuilder">The module builder from the assembly in which it is to be created</param>
/// <param name="serviceContract">The service contract to create</param>
/// <param name="serviceImplementation">The ClientBase which is to be wrapped.</param>
/// <returns>An implementation of the service contract.</returns>
/// <remarks>
/// This method generates MSIL for a ClientBase that inherits the service contract. Effectively, each loop creates a method that looks like this:
///
/// public ReturnType MethodCall(ParameterType1 parm1,ParameterType2 parm2, ParameterType3 parm3)
/// {
/// var service = new ServiceContractImplementation();
/// var results = service.MethodCall(parm1,parm2,parm3);
/// try
/// {
/// service.Close();
/// }
/// catch(Exception e)
/// {
/// service.Abort();
/// }
/// return results;
/// }
/// This pattern of try->close/catch->abort is recomended over the using statement here http://msdn.microsoft.com/en-us/library/aa355056.aspx
/// </remarks>
[DebuggerStepThrough]
private static Type CreateAgent(ModuleBuilder moduleBuilder, Type serviceContract, Type serviceImplementation)
{
var typeName = serviceContract.Name + "_DisposingAgent";
TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, null, new[] { serviceContract }); // create the type
foreach (var iMethod in serviceContract.GetMethods())
{
var methodParameters = iMethod.GetParameters().Select(s => s.ParameterType).ToArray(); // get method parametes
var method = typeBuilder.DefineMethod(iMethod.Name, // create method signature
MethodAttributes.Public | MethodAttributes.Virtual,
iMethod.ReturnType,
methodParameters);
// get methods used in invocation.
var closeMethod = serviceImplementation.GetMethod("Close", BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
var abortMethod = serviceImplementation.GetMethod("Abort", BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
var implementationMethod = serviceImplementation.GetMethod(iMethod.Name, methodParameters);
var serviceConstructor = serviceImplementation.GetConstructor(new[] { typeof(string) });
var getBindingName = typeof(NameValueCollection).GetMethod("Get",new[] { typeof(string) });
var getAppSettings = typeof(ConfigurationManager).GetMethod("get_AppSettings");
var il = method.GetILGenerator(); // start msil
// declare local variables
var localService = il.DeclareLocal(serviceImplementation);
var localResult = il.DeclareLocal(implementationMethod.ReturnType);
//var localDefaultBindingName = il.DeclareLocal(typeof(string));
//il.Emit(OpCodes.Call, getAppSettings); // get the app settings
//il.Emit(OpCodes.Ldstr, "defaultBindingName"); // load the appsettings name onto the stack
//il.Emit(OpCodes.Call, getBindingName); // get the binding name and put it on the stack
//il.Emit(OpCodes.Stloc, localDefaultBindingName); // save the name
//il.Emit(OpCodes.Ldloc, localDefaultBindingName); // load the name as the first argument of the contstructor
il.Emit(OpCodes.Ldstr, serviceContract.Name);
il.Emit(OpCodes.Newobj, serviceConstructor); // create service
il.Emit(OpCodes.Stloc, localService); // store the new service on the stack
il.Emit(OpCodes.Ldloc, localService);
for (int i = 1; i <= methodParameters.Count(); i++) // load each argument into stack for use in method invocation
il.Emit(OpCodes.Ldarg, i);
il.Emit(OpCodes.Callvirt, implementationMethod); // invoke the method on the implementation
il.Emit(OpCodes.Stloc, localResult);
il.BeginExceptionBlock(); // begin try block
il.Emit(OpCodes.Ldloc, localService);
il.Emit(OpCodes.Callvirt, closeMethod); // close
il.BeginCatchBlock(typeof(Exception)); // catch
il.Emit(OpCodes.Ldloc, localService);
il.Emit(OpCodes.Callvirt, abortMethod); // abort
il.EndExceptionBlock(); // end tr/catch block
il.Emit(OpCodes.Ldloc, localResult);
il.Emit(OpCodes.Ret); // return results from generated method
}
return typeBuilder.CreateType();
}
/// <summary>
/// Dependancy injection sub class for the ServiceAgentFactory
/// </summary>
public class Injection
{
/// <summary>
/// Injects an implementation type into the available agent types. The implementation type will be instantiated directly when invokeing GetService.
/// </summary>
/// <param name="serviceContract"></param>
/// <param name="implementationType"></param>
[DebuggerStepThrough]
public static void InjectAgentType(Type serviceContract, Type implementationType)
{
// ensure that the _AgentTypes dictionary exists
if (_AgentTypes == null)
_AgentTypes = new Dictionary<Type, Type>();
if (!serviceContract.IsAssignableFrom(implementationType))
throw new ArgumentException("implementationType must inherit from serviceContract");
ClearAgentType(serviceContract);
_AgentTypes.Add(serviceContract, implementationType);
}
/// <summary>
/// Injects a delegate into the available agents. The delegate will be fired instead of the dynamic generator when invoking GetService.
/// </summary>
/// <param name="serviceContract"></param>
/// <param name="implementationType"></param>
/// <remarks>
/// This method is primarily intended for use during unit testing.
/// </remarks>
[DebuggerStepThrough]
public static void InjectAgentDelegate<TServiceContract>(Func<TServiceContract> agentDelegate)
{
Type serviceContract = typeof(TServiceContract);
// ensure that the _AgentTypes dictionary exists
if (_AgentDelegates == null)
_AgentDelegates = new Dictionary<Type, Delegate>();
ClearAgentType(serviceContract);
_AgentDelegates.Add(serviceContract, agentDelegate);
}
/// <summary>
/// Injects a base type into available types and wraps it with a generated agent. The implementation will be instantiated within the agent and have Close(), and Abort() on exception, called on completion.
/// </summary>
/// <param name="serviceContract"></param>
/// <param name="clientBaseType"></param>
/// <remarks>
/// This method is primarily intended for use during unit testing.
/// </remarks>
[DebuggerStepThrough]
public static void InjectBaseType(Type serviceContract, Type clientBaseType)
{
// ensure that the _AgentTypes dictionary exists
if (_AgentTypes == null)
_AgentTypes = new Dictionary<Type, Type>();
if (!serviceContract.IsAssignableFrom(clientBaseType))
throw new ArgumentException("implementationType must inherit from serviceContract");
if (clientBaseType.GetMethod("Close", System.Type.EmptyTypes) == null)
throw new ArgumentException("implementationType must contain Close() method");
if (clientBaseType.GetMethod("Abort", System.Type.EmptyTypes) == null)
throw new ArgumentException("implementationType must contain Abort() method");
ClearAgentType(serviceContract);
GenerateImplementationType(serviceContract, clientBaseType);
}
/// <summary>
/// Removes and agent from the available agent types.
/// </summary>
/// <param name="serviceContract"></param>
[DebuggerStepThrough]
public static void ClearAgentType(Type serviceContract)
{
// ensure that the _AgentTypes dictionary exists
if (_AgentTypes == null)
_AgentTypes = new Dictionary<Type, Type>();
if (_AgentDelegates == null)
_AgentDelegates = new Dictionary<Type, Delegate>();
_AgentTypes.Remove(serviceContract);
_AgentDelegates.Remove(serviceContract);
}
}
}
}
@miguelludert
Copy link
Author

This class reads the configurations for WCF from the web.config and creates a generic web service factory, using reflection, to avoid having to use Visual Studio service references. This tool auto-updates based on the interfaces used for the web services.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment