将接口方法作为参数传递
Passing an interface method as a parameter
注意:这很可能是非常C#
的特定语言问题,与WCF
或web services
完全无关。
有一个 3 方 ASMX
网络服务,用于数据检索。我创建了一个名为 ExecuteCommand()
的通用方法,用于针对 Web 服务的每个请求。该方法的作用是处理cookiesession/exceptions等常用逻辑。对于每个请求,都应使用一个新通道,以简化未使用资源的处理。
问题是要使用 ExecuteCommand()
方法 - 我每次都必须初始化一个通道,以便能够将要执行的方法作为参数传递。对不起,如果这听起来太复杂了。这是一个用法示例:
string color = "blue";
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
var cars = WcfHelper.ExecuteCommand(channel, () => channel.GetCars(color));
// channel is null here. Channel was closed/aborted, depending on Exception type.
调用 ExecuteCommand()
后 - channel
已被处理掉。之所以需要 channel
对象,是为了能够提供一个作为参数执行的方法!即() => channel.GetCars()
。为了进一步支持这些话,这里是 WcfHelper
class 内部:
public static class WcfHelper
{
public static Cookie Cookie { get; set; }
public static T ExecuteCommand<T>(IClientChannel channel, Expression<Func<T>> method)
{
T result = default(T);
try
{
// init operation context
using (new OperationContextScope(channel))
{
// set the session cookie to header
if (Cookie != null) {
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers["Cookie"] = cookie.ToString();
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request;
}
// execute method
var compiledMethod = method.Compile();
result = compiledMethod.Invoke();
}
}
// do different logic for FaultException, CommunicationException, TimeoutException
catch (Exception)
{
throw;
}
finally
{
CloseOrAbortServiceChannel(channel);
channel = null;
}
return result;
}
private static void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
{
bool isClosed = false;
if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
return;
try
{
if (communicationObject.State != CommunicationState.Faulted)
{
communicationObject.Close();
isClosed = true;
}
}
catch (Exception)
{
throw;
}
finally
{
if (!isClosed)
AbortServiceChannel(communicationObject);
}
}
private static void AbortServiceChannel(ICommunicationObject communicationObject)
{
try
{
communicationObject.Abort();
}
catch (Exception)
{
throw;
}
}
}
所以简短的问题 - 可以在 ExecuteCommand
方法本身中初始化一个 channel
变量,同时可以定义,哪个方法应在 ExecuteCommand
中执行对于给定的频道?
我正在努力完成这样的事情:
string color = "blue";
var cars = WcfHelper.ExecuteCommand<Car[], CarServiceSoapChannel>(channel => channel.GetCars(color));
甚至
string color = "blue";
var cars = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.GetCars(color));
当然欢迎任何其他代码改进建议,但不是强制性的。
P.S。 ASMX
作为 Service reference
添加到 Visual Studio
中。因此,有一些为 "CarService" 自动生成的实体,例如 - CarServiceSoapChannel
接口,CarServiceSoapClient
class,当然 CarService
接口包含一个方法网络服务。在上面的示例中,ChannelFactory
用于为 CarServiceSoapChannel
界面创建通道,因此,这里是问题名称的来源:Passing an interface method as a parameter
。这可能有点误导,但我希望从描述本身可以清楚地了解我试图完成的目标。
更新 25.05.2018
我听从了@nvoigt 的建议并获得了我想要的结果:
public static TResult ExecuteCommand<TInterface, TResult>(Func<TInterface, TResult> method)
where TInterface : IClientChannel
{
TResult result = default(TResult);
IClientChannel channel = null;
try
{
channel = StrategyFactory.CreateChannel<TInterface>();
// init operation context
using (new OperationContextScope(channel))
{
// set the session cookie to header
if (Cookie != null)
Cookie.SetCookieForSession();
// execute method
result = method((TInterface)channel);
}
}
catch (Exception)
{
throw;
}
finally
{
CloseOrAbortServiceChannel(channel);
channel = null;
}
return result;
}
这已经达到了最初的目的。然而,这种方法有一个问题。为了调用该方法 - 您必须显式指定方法的 return 参数。
这么说吧,如果你想调用方法-写这个是不够的:
var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.IsBlue())
您必须特别指定,return 类型应为 boolean
var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel, bool>(channel => channel.IsBlue())
我个人不介意多写一点代码,因为它仍然比我最初的方法实现有很大优势,但是,我想知道在新版本中是否可以改进?
例如,当我在方法中只有 1 个泛型 TResult
类型时 - return 类型 <TResult>
可以省略。 2 个泛型不再是这种情况。无论如何,谢谢@nvoigt!
您的方法签名应该是:
public static TResult ExecuteCommand<TInterface>(Func<TInterface, TResult> method)
然后在您的 WcfHelper 中(可能不再是 static
,因为它需要一个成员 _strategyFactory
),您像以前一样创建一个频道:
{
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
return method(channel);
}
显然,您需要再次添加所有花哨的 try/finally 内容。
由于您现在无论如何都应该拥有 class 中的工厂作为成员的实例,因此您可以将通用服务合同放入 class 以方便用户使用:
public class ConnectionToService<TInterface> : where TInterface : class
{
public TResult ExecuteCommand<TResult>(Func<TInterface, TResult> method)
{
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
return method(channel);
}
}
用法:
var service = new ConnectionToService<ICarService>();
var color = service.ExecuteCommand(s => s.GetColor());
注意:这很可能是非常C#
的特定语言问题,与WCF
或web services
完全无关。
有一个 3 方 ASMX
网络服务,用于数据检索。我创建了一个名为 ExecuteCommand()
的通用方法,用于针对 Web 服务的每个请求。该方法的作用是处理cookiesession/exceptions等常用逻辑。对于每个请求,都应使用一个新通道,以简化未使用资源的处理。
问题是要使用 ExecuteCommand()
方法 - 我每次都必须初始化一个通道,以便能够将要执行的方法作为参数传递。对不起,如果这听起来太复杂了。这是一个用法示例:
string color = "blue";
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
var cars = WcfHelper.ExecuteCommand(channel, () => channel.GetCars(color));
// channel is null here. Channel was closed/aborted, depending on Exception type.
调用 ExecuteCommand()
后 - channel
已被处理掉。之所以需要 channel
对象,是为了能够提供一个作为参数执行的方法!即() => channel.GetCars()
。为了进一步支持这些话,这里是 WcfHelper
class 内部:
public static class WcfHelper
{
public static Cookie Cookie { get; set; }
public static T ExecuteCommand<T>(IClientChannel channel, Expression<Func<T>> method)
{
T result = default(T);
try
{
// init operation context
using (new OperationContextScope(channel))
{
// set the session cookie to header
if (Cookie != null) {
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers["Cookie"] = cookie.ToString();
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request;
}
// execute method
var compiledMethod = method.Compile();
result = compiledMethod.Invoke();
}
}
// do different logic for FaultException, CommunicationException, TimeoutException
catch (Exception)
{
throw;
}
finally
{
CloseOrAbortServiceChannel(channel);
channel = null;
}
return result;
}
private static void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
{
bool isClosed = false;
if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
return;
try
{
if (communicationObject.State != CommunicationState.Faulted)
{
communicationObject.Close();
isClosed = true;
}
}
catch (Exception)
{
throw;
}
finally
{
if (!isClosed)
AbortServiceChannel(communicationObject);
}
}
private static void AbortServiceChannel(ICommunicationObject communicationObject)
{
try
{
communicationObject.Abort();
}
catch (Exception)
{
throw;
}
}
}
所以简短的问题 - 可以在 ExecuteCommand
方法本身中初始化一个 channel
变量,同时可以定义,哪个方法应在 ExecuteCommand
中执行对于给定的频道?
我正在努力完成这样的事情:
string color = "blue";
var cars = WcfHelper.ExecuteCommand<Car[], CarServiceSoapChannel>(channel => channel.GetCars(color));
甚至
string color = "blue";
var cars = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.GetCars(color));
当然欢迎任何其他代码改进建议,但不是强制性的。
P.S。 ASMX
作为 Service reference
添加到 Visual Studio
中。因此,有一些为 "CarService" 自动生成的实体,例如 - CarServiceSoapChannel
接口,CarServiceSoapClient
class,当然 CarService
接口包含一个方法网络服务。在上面的示例中,ChannelFactory
用于为 CarServiceSoapChannel
界面创建通道,因此,这里是问题名称的来源:Passing an interface method as a parameter
。这可能有点误导,但我希望从描述本身可以清楚地了解我试图完成的目标。
更新 25.05.2018 我听从了@nvoigt 的建议并获得了我想要的结果:
public static TResult ExecuteCommand<TInterface, TResult>(Func<TInterface, TResult> method)
where TInterface : IClientChannel
{
TResult result = default(TResult);
IClientChannel channel = null;
try
{
channel = StrategyFactory.CreateChannel<TInterface>();
// init operation context
using (new OperationContextScope(channel))
{
// set the session cookie to header
if (Cookie != null)
Cookie.SetCookieForSession();
// execute method
result = method((TInterface)channel);
}
}
catch (Exception)
{
throw;
}
finally
{
CloseOrAbortServiceChannel(channel);
channel = null;
}
return result;
}
这已经达到了最初的目的。然而,这种方法有一个问题。为了调用该方法 - 您必须显式指定方法的 return 参数。
这么说吧,如果你想调用方法-写这个是不够的:
var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.IsBlue())
您必须特别指定,return 类型应为 boolean
var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel, bool>(channel => channel.IsBlue())
我个人不介意多写一点代码,因为它仍然比我最初的方法实现有很大优势,但是,我想知道在新版本中是否可以改进?
例如,当我在方法中只有 1 个泛型 TResult
类型时 - return 类型 <TResult>
可以省略。 2 个泛型不再是这种情况。无论如何,谢谢@nvoigt!
您的方法签名应该是:
public static TResult ExecuteCommand<TInterface>(Func<TInterface, TResult> method)
然后在您的 WcfHelper 中(可能不再是 static
,因为它需要一个成员 _strategyFactory
),您像以前一样创建一个频道:
{
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
return method(channel);
}
显然,您需要再次添加所有花哨的 try/finally 内容。
由于您现在无论如何都应该拥有 class 中的工厂作为成员的实例,因此您可以将通用服务合同放入 class 以方便用户使用:
public class ConnectionToService<TInterface> : where TInterface : class
{
public TResult ExecuteCommand<TResult>(Func<TInterface, TResult> method)
{
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
return method(channel);
}
}
用法:
var service = new ConnectionToService<ICarService>();
var color = service.ExecuteCommand(s => s.GetColor());