将接口方法作为参数传递

3
注意:这可能是一个非常与C#相关的语言问题,与WCF或Web服务无关。
有一个第三方ASMX Web服务,用于数据检索。我创建了一个通用方法叫做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对象,是为了能够将一个方法作为参数传递!例如:() => 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 变量并定义在给定通道中执行的方法,这种做法可行吗?
我想实现这样的功能:
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. 在Visual Studio中,ASMX作为服务引用添加。因此,“CarService”自动生成了一些实体,例如-CarServiceSoapChannel接口,CarServiceSoapClient类和当然包含Web服务方法的CarService接口。在上面的示例中,使用ChannelFactory创建了一个CarServiceSoapChannel接口的通道,因此,这就是问题名称的来源:将接口方法作为参数传递。这可能有点误导,但我希望从描述本身就可以清楚地了解我试图实现什么。

更新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())

个人而言,我不介意再写一些代码,因为这仍然比我的初始方法实现有很大优势。但是,我想知道新版本能否改进?

例如,当方法中只有一个通用的 TResult 类型时,返回类型 <TResult> 可以被省略。但是在有两个通用类型的情况下,这种省略就不再适用了。无论如何,感谢 @nvoigt


第二种方法在我看来相当不错。你可以在 ExecuteCommand 中创建/初始化通道,并将其作为参数传递给匿名方法。这有什么问题吗? - MakePeaceGreatAgain
@HimBromBeere 第二种方法?您可能是指 var cars = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.GetCars(color)); 吗?这是我正在尝试实现的一种首选方式来解决此问题。该方法目前还不能正常工作。 :) 因此,这里有一个问题。 - Alex
1
你所需要做的就是提供一个通道给你通过compileMethod.Compile()创建的Func,假设你的funcFunc<Channel, T> - MakePeaceGreatAgain
@HimBromBeere,这个方法很好用!感谢您对此问题的建议!nvoigt在答案部分提供了一个很好的示例。 - Alex
1个回答

2

你的方法签名应该是:

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<Func<T>> 方法 的重点.. 我需要检查 ExpressionFunc 之间的区别。我按照你的建议进行了重构代码。谢谢! - Alex
有没有可能改进这个方法,使得 TResult 成为一个隐式类型?请查看我的最新更新。 - Alex
1
@Alex 上次我写了这样一个类,我把 TInterface 泛型类型放在了类里面。我会添加一个例子。 - nvoigt
太好了!谢谢你! - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接