我过去所做的是有一个通用客户端(使用 Unity 进行“拦截”),它根据服务的业务接口从 ChannelFactory 为每次调用创建一个新连接,并在每次调用后关闭该连接,决定关于是否根据返回异常或正常响应来指示连接发生故障。 (见下文。)
我使用这个客户端的真实代码只是请求一个实现业务接口的实例,它将获得这个通用包装器的一个实例。返回的实例不需要根据是否返回异常来处理或区别对待。要获得服务客户端(使用下面的包装器),我的代码是:var client = SoapClientInterceptorBehavior<T>.CreateInstance(new ChannelFactory<T>("*")),它通常隐藏在注册表中或作为构造函数参数传入。因此,在您的情况下,我最终会得到var myClass = new MyClass(SoapClientInterceptorBehavior<IServiceClient>.CreateInstance(new ChannelFactory<IServiceClient>("*")));(您可能希望将整个调用以在您自己的某些工厂方法中创建实例,只需要 IServiceClient 作为输入类型,以使其更具可读性。;- ))
在我的测试中,我可以只注入一个模拟的服务实现,并测试是否调用了正确的业务方法以及是否正确处理了它们的结果。
/// <summary>
/// IInterceptionBehavior that will request a new channel from a ChannelFactory for each call,
/// and close (or abort) it after each call.
/// </summary>
/// <typeparam name="T">business interface of SOAP service to call</typeparam>
public class SoapClientInterceptorBehavior<T> : IInterceptionBehavior
{
// create a logger to include the interface name, so we can configure log level per interface
// Warn only logs exceptions (with arguments)
// Info can be enabled to get overview (and only arguments on exception),
// Debug always provides arguments and Trace also provides return value
private static readonly Logger Logger = LogManager.GetLogger(LoggerName());
private static string LoggerName()
{
string baseName = MethodBase.GetCurrentMethod().DeclaringType.FullName;
baseName = baseName.Remove(baseName.IndexOf('`'));
return baseName + "." + typeof(T).Name;
}
private readonly Func<T> _clientCreator;
/// <summary>
/// Creates new, using channelFactory.CreatChannel to create a channel to the SOAP service.
/// </summary>
/// <param name="channelFactory">channelfactory to obtain connections from</param>
public SoapClientInterceptorBehavior(ChannelFactory<T> channelFactory)
: this(channelFactory.CreateChannel)
{
}
/// <summary>
/// Creates new, using the supplied method to obtain a connection per call.
/// </summary>
/// <param name="clientCreationFunc">delegate to obtain client connection from</param>
public SoapClientInterceptorBehavior(Func<T> clientCreationFunc)
{
_clientCreator = clientCreationFunc;
}
/// <summary>
/// Intercepts calls to SOAP service, ensuring proper creation and closing of communication
/// channel.
/// </summary>
/// <param name="input">invocation being intercepted.</param>
/// <param name="getNext">next interceptor in chain (will not be called)</param>
/// <returns>result from SOAP call</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
Logger.Info(() => "Invoking method: " + input.MethodBase.Name + "()");
// we will not invoke an actual target, or call next interception behaviors, instead we will
// create a new client, call it, close it if it is a channel, and return its
// return value.
T client = _clientCreator.Invoke();
Logger.Trace(() => "Created client");
var channel = client as IClientChannel;
IMethodReturn result;
int size = input.Arguments.Count;
var args = new object[size];
for(int i = 0; i < size; i++)
{
args[i] = input.Arguments[i];
}
Logger.Trace(() => "Arguments: " + string.Join(", ", args));
try
{
object val = input.MethodBase.Invoke(client, args);
if (Logger.IsTraceEnabled)
{
Logger.Trace(() => "Completed " + input.MethodBase.Name + "(" + string.Join(", ", args) + ") return-value: " + val);
}
else if (Logger.IsDebugEnabled)
{
Logger.Debug(() => "Completed " + input.MethodBase.Name + "(" + string.Join(", ", args) + ")");
}
else
{
Logger.Info(() => "Completed " + input.MethodBase.Name + "()");
}
result = input.CreateMethodReturn(val, args);
if (channel != null)
{
Logger.Trace("Closing channel");
channel.Close();
}
}
catch (TargetInvocationException tie)
{
// remove extra layer of exception added by reflective usage
result = HandleException(input, args, tie.InnerException, channel);
}
catch (Exception e)
{
result = HandleException(input, args, e, channel);
}
return result;
}
private static IMethodReturn HandleException(IMethodInvocation input, object[] args, Exception e, IClientChannel channel)
{
if (Logger.IsWarnEnabled)
{
// we log at Warn, caller might handle this without need to log
string msg = string.Format("Exception from " + input.MethodBase.Name + "(" + string.Join(", ", args) + ")");
Logger.Warn(msg, e);
}
IMethodReturn result = input.CreateExceptionMethodReturn(e);
if (channel != null)
{
Logger.Trace("Aborting channel");
channel.Abort();
}
return result;
}
/// <summary>
/// Returns the interfaces required by the behavior for the objects it intercepts.
/// </summary>
/// <returns>
/// The required interfaces.
/// </returns>
public IEnumerable<Type> GetRequiredInterfaces()
{
return new [] { typeof(T) };
}
/// <summary>
/// Returns a flag indicating if this behavior will actually do anything when invoked.
/// </summary>
/// <remarks>
/// This is used to optimize interception. If the behaviors won't actually
/// do anything (for example, PIAB where no policies match) then the interception
/// mechanism can be skipped completely.
/// </remarks>
public bool WillExecute
{
get { return true; }
}
/// <summary>
/// Creates new client, that will obtain a fresh connection before each call
/// and closes the channel after each call.
/// </summary>
/// <param name="factory">channel factory to connect to service</param>
/// <returns>instance which will have SoapClientInterceptorBehavior applied</returns>
public static T CreateInstance(ChannelFactory<T> factory)
{
IInterceptionBehavior behavior = new SoapClientInterceptorBehavior<T>(factory);
return (T)Intercept.ThroughProxy<IMy>(
new MyClass(),
new InterfaceInterceptor(),
new[] { behavior });
}
/// <summary>
/// Dummy class to use as target (which will never be called, as this behavior will not delegate to it).
/// Unity Interception does not allow ONLY interceptor, it needs a target instance
/// which must implement at least one public interface.
/// </summary>
public class MyClass : IMy
{
}
/// <summary>
/// Public interface for dummy target.
/// Unity Interception does not allow ONLY interceptor, it needs a target instance
/// which must implement at least one public interface.
/// </summary>
public interface IMy
{
}
}