将接口方法作为参数传递

时间:2018-05-24 14:27:30

标签: c# wcf asmx

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

有一个3方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对象的原因是能够提供一个作为参数执行的方法!即。() => 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;
        }
    }
}

所以简短的问题 - 可以在channel方法本身内初始化ExecuteCommand变量,同时有可能定义,哪个方法应在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。 ASMXService reference中添加Visual Studio。因此,有一些实体为“CarService”自动生成,例如 - CarServiceSoapChannel接口,CarServiceSoapClient类,当然还包含CarService接口,包含Web服务的方法。在上面的示例中,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类型时 - 可以省略返回类型<TResult>。 2个泛型不再是这种情况。无论如何,谢谢@ nvoigt

1 个答案:

答案 0 :(得分: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());