Підсумок
Використовуючи методи, описані у цій відповіді, можна споживати послугу WCF у використанні блоку із наступним синтаксисом:
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Звичайно, ви можете ще більше адаптувати це, щоб досягти більш стислої моделі програмування, характерної для вашої ситуації - але справа в тому, що ми можемо створити реалізацію IMyService
каналу, який правильно реалізує одноразову схему.
Деталі
Усі відповіді, що надані до цього часу, стосуються проблеми подолання "помилки" в реалізації каналу WCF IDisposable
. Відповідь, яка, здається, пропонує найбільш стисну модель програмування (що дозволяє використовувати using
блок для розпорядження некерованими ресурсами), це ця - де проксі модифікований для реалізації IDisposable
з помилковою реалізацією. Проблема такого підходу полягає в ремонтопридатності - нам доведеться знову реалізувати цю функціональність для будь-якого проксі, який ми використовуємо. На варіації цієї відповіді ми побачимо, як ми можемо використовувати композицію, а не успадкування, щоб зробити цю техніку загальною.
Перша спроба
Здійснюється реалізація різних IDisposable
варіантів реалізації, але заради аргументу ми використаємо адаптацію тієї, яку використовує прийнятий на даний момент відповідь .
[ServiceContract]
public interface IMyService
{
[OperationContract]
void DoWork();
}
public class ProxyDisposer : IDisposable
{
private IClientChannel _clientChannel;
public ProxyDisposer(IClientChannel clientChannel)
{
_clientChannel = clientChannel;
}
public void Dispose()
{
var success = false;
try
{
_clientChannel.Close();
success = true;
}
finally
{
if (!success)
_clientChannel.Abort();
_clientChannel = null;
}
}
}
public class ProxyWrapper : IMyService, IDisposable
{
private IMyService _proxy;
private IDisposable _proxyDisposer;
public ProxyWrapper(IMyService proxy, IDisposable disposable)
{
_proxy = proxy;
_proxyDisposer = disposable;
}
public void DoWork()
{
_proxy.DoWork();
}
public void Dispose()
{
_proxyDisposer.Dispose();
}
}
Озброївшись вищевказаними класами, ми можемо зараз писати
public class ServiceHelper
{
private readonly ChannelFactory<IMyService> _channelFactory;
public ServiceHelper(ChannelFactory<IMyService> channelFactory )
{
_channelFactory = channelFactory;
}
public IMyService CreateChannel()
{
var channel = _channelFactory.CreateChannel();
var channelDisposer = new ProxyDisposer(channel as IClientChannel);
return new ProxyWrapper(channel, channelDisposer);
}
}
Це дозволяє нам споживати нашу послугу за допомогою using
блоку:
ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Зробити це загальним
Все, що ми зробили поки, - це переформулювати рішення Томаса . Що заважає цьому коду бути загальним, це той факт, що ProxyWrapper
клас повинен бути повторно реалізований для кожного контракту на обслуговування, який ми хочемо. Зараз ми розглянемо клас, який дозволяє нам динамічно створювати цей тип за допомогою IL:
public class ServiceHelper<T>
{
private readonly ChannelFactory<T> _channelFactory;
private static readonly Func<T, IDisposable, T> _channelCreator;
static ServiceHelper()
{
/**
* Create a method that can be used generate the channel.
* This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
* */
var assemblyName = Guid.NewGuid().ToString();
var an = new AssemblyName(assemblyName);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));
var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
new[] { typeof(T), typeof(IDisposable) });
var ilGen = channelCreatorMethod.GetILGenerator();
var proxyVariable = ilGen.DeclareLocal(typeof(T));
var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
ilGen.Emit(OpCodes.Ldarg, proxyVariable);
ilGen.Emit(OpCodes.Ldarg, disposableVariable);
ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
ilGen.Emit(OpCodes.Ret);
_channelCreator =
(Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));
}
public ServiceHelper(ChannelFactory<T> channelFactory)
{
_channelFactory = channelFactory;
}
public T CreateChannel()
{
var channel = _channelFactory.CreateChannel();
var channelDisposer = new ProxyDisposer(channel as IClientChannel);
return _channelCreator(channel, channelDisposer);
}
/**
* Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
* This method is actually more generic than this exact scenario.
* */
private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
{
TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
TypeAttributes.Public | TypeAttributes.Class);
var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));
#region Constructor
var constructorBuilder = tb.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName,
CallingConventions.Standard,
interfacesToInjectAndImplement);
var il = constructorBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));
for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg, i);
il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
}
il.Emit(OpCodes.Ret);
#endregion
#region Add Interface Implementations
foreach (var type in interfacesToInjectAndImplement)
{
tb.AddInterfaceImplementation(type);
}
#endregion
#region Implement Interfaces
foreach (var type in interfacesToInjectAndImplement)
{
foreach (var method in type.GetMethods())
{
var methodBuilder = tb.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
MethodAttributes.Final | MethodAttributes.NewSlot,
method.ReturnType,
method.GetParameters().Select(p => p.ParameterType).ToArray());
il = methodBuilder.GetILGenerator();
if (method.ReturnType == typeof(void))
{
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeFields[type]);
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Ret);
}
else
{
il.DeclareLocal(method.ReturnType);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeFields[type]);
var methodParameterInfos = method.GetParameters();
for (var i = 0; i < methodParameterInfos.Length; i++)
il.Emit(OpCodes.Ldarg, (i + 1));
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Stloc_0);
var defineLabel = il.DefineLabel();
il.Emit(OpCodes.Br_S, defineLabel);
il.MarkLabel(defineLabel);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
}
tb.DefineMethodOverride(methodBuilder, method);
}
}
#endregion
return tb.CreateType();
}
}
З нашим новим класом помічників ми можемо зараз писати
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Зауважте, що ви також можете використовувати ту саму техніку (з незначними модифікаціями) для автоматично сформованих клієнтів, що передають у спадок ClientBase<>
(замість того, щоб використовувати ChannelFactory<>
), або якщо ви хочете використовувати іншу реалізацію IDisposable
для закриття свого каналу.