将接口方法作为参数传递

Passing an interface method as a parameter

注意:这很可能是非常C#的特定语言问题,与WCFweb 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());