【问题标题】:Passing an interface method as a parameter将接口方法作为参数传递
【发布时间】:2018-05-24 14:27:29
【问题描述】:

注意:这很可能是 C# 特定语言问题,与 WCFweb services 完全无关。

有一个三方ASMX 网络服务,用于数据检索。我创建了一个名为ExecuteCommand() 的通用方法,用于针对Web 服务的每个请求。此方法的目的是处理 cookie 会话/异常和其他常见逻辑。对于每个请求,都应使用一个新通道,以简化未使用资源的处理。

问题是要使用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 对象,是为了能够提供一个方法作为参数来执行!即() =&gt; channel.GetCars()。为了进一步支持这些话,这里是WcfHelper 类内部:

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));

欢迎任何其他代码改进建议,但当然不是强制性的。

附: ASMXVisual Studio 中添加为Service reference。因此,为“CarService”自动生成了一些实体,例如 - CarServiceSoapChannel 接口、CarServiceSoapClient 类,当然还有包含 Web 服务方法的 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;
}

这已经达到了最初的目标。然而,这种方法存在一个问题。为了调用该方法 - 您必须明确指定该方法的返回参数。

也就是说,如果你想调用方法——写这个是不够的:

var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.IsBlue())

您必须特别指定,返回类型应为boolean

var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel, bool>(channel => channel.IsBlue())

我个人不介意多写一点代码,因为它仍然比我最初的方法实现有很大的优势,但是,我想知道在新版本中是否可以改进?

例如,当我在方法中只有 1 个通用 TResult 类型时 - 返回类型 &lt;TResult&gt; 可以省略。这不再是 2 个泛型的情况。无论如何,谢谢@nvoigt

【问题讨论】:

  • 第二种方法在我看来相当不错。您在ExecuteCommand 中创建/初始化通道并将其作为参数传递给匿名方法。它有什么问题?
  • @HimBromBeere 第二种方法?你可能是说var cars = WcfHelper.ExecuteCommand&lt;CarServiceSoapChannel&gt;(channel =&gt; channel.GetCars(color));?这是我正在努力实现的。面对这个问题的首选方式。该方法还不能那样工作。 :) 因此这是问题所在。
  • 您所要做的就是为您由compileMethod.Compile() 创建的Func 提供频道 - 假设您的功能是Func&lt;Channel, T&gt;
  • @HimBromBeere 这很好用!感谢您对这个问题的意见! nvoigt 在回答部分为这个建议做了一个很好的例子。

标签: c# wcf asmx


【解决方案1】:

你的方法签名应该是:

public static TResult ExecuteCommand<TInterface>(Func<TInterface, TResult> method)

然后在您的 WcfHelper(可能不再是 static,因为它需要一个成员 _strategyFactory)中,您像以前一样创建一个频道:

{
    var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();

    return method(channel);
}

显然,您需要再次添加所有花哨的 try/finally 内容。


由于您现在应该将类中的工厂作为成员拥有实例,因此您可以将服务合同泛型放入您的类中,以方便用户使用:

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());

【讨论】:

  • 看来我完全错过了在我的实现中使用Expression&lt;Func&lt;T&gt;&gt; method 的意义。我需要检查ExpressionFunc 的差异。我听从了您的建议并相应地重构了代码。谢谢!
  • 是否可以改进方法,所以TResult 将是一个隐式类型?查看我的最新更新
  • @Alex 上次写这样一个类的时候,我把TInterface泛型类型放到了类中。我将添加一个示例。
  • 太棒了!谢谢!
猜你喜欢
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 2020-12-20
  • 2010-11-16
相关资源
最近更新 更多