Last active
September 29, 2016 21:26
-
-
Save miguelludert/39b8e115e29112297145 to your computer and use it in GitHub Desktop.
Service Agent Factory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<> Method, dynamically selects the method and invokes it. | |
/// </remarks> | |
/// <example> | |
/// var serviceResults = ServiceAgentFactory.GetService<I_ServiceContract>().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); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.