简体   繁体   English

使用WCF“远程处理”.NET类库的最佳实践

[英]Best practices on “remoting” a .NET class library with WCF

I have a large .NET 4.5 class library containing 60+ classes and many public methods. 我有一个包含60多个类和许多公共方法的大型.NET 4.5类库。 This works as a programmatic interface to my app. 这可以作为我的应用程序的编程接口。 Now I would like to invoke this library over the network using WCF. 现在我想使用WCF通过网络调用此库。 What are the best practices to do this? 这样做的最佳做法是什么?

A naïve approach would be to wrap my class library with a WCF service library that replicates the class and method structure of the original class library, having one method for each method in it. 一种天真的方法是使用WCF服务库来包装我的类库,该服务库复制原始类库的类和方法结构,其中每个方法都有一个方法。 However, this seems to be overkill and go against the principle to make chunky rather than chatty network interfaces. 然而,这似乎是过度的,并且违背了制造粗糙而不是繁琐的网络接口的原则。 How should I construct the WCF service library then? 那我该如何构建WCF服务库呢? What structure should it have? 它应该有什么结构? Is there any recognised best practice guidance on this? 有没有公​​认的最佳实践指导? Thanks. 谢谢。

Don't know if it's a "best" practice, but I've seen this done: 不知道这是否是“最佳”练习,但我已经看到了这样做:

Make the data contract a single method that takes as arguments the operation and an array of parameters. 使数据契约成为一个单独的方法,该方法将操作和参数数组作为参数。 Then, on the server, get the implementing method via reflection and simply pass the array of parameters. 然后,在服务器上,通过反射获取实现方法,并简单地传递参数数组。 Here's a quick sample I've put together (and tested, it works). 这是我放在一起的快速样本(经过测试,可行)。

Contract: 合同:

namespace GenericWcfService
{
    [ServiceKnownType(typeof(Pair))] //there's another way to add more, look it up
    [ServiceContract]
    public interface ICalculatorService
    {
        [OperationContract]
        OperationResult GetResult(Operation op, object[] parameteres);
    }

    public enum Operation
    {
        Add,
        Substract,
        Multiply,
        Divide,
        Print,
        AddPair
    }

    [DataContract]
    public class OperationResult
    {
        [DataMember]
        public object Result { get; set; }

        [DataMember]
        public string Error { get; set; }
    }

    [DataContract]
    public class Pair
    {
        [DataMember]
        public int V1;
        [DataMember]
        public int V2;
    }
}

Server: 服务器:

namespace GenericWcfService
{
    public class CalculatorService : ICalculatorService
    {
        public OperationResult GetResult(Operation op, object[] parameteres)
        {
            var calc = new CalculatorImpl();
            var method = typeof(CalculatorImpl).GetMethod(op.ToString());

            var result = new OperationResult();
            if (method == null) { result.Error = "Incompatible"; return result; }
            var mParameters = method.GetParameters();
            if (mParameters.Length != parameteres.Length) { result.Error = "Incompatible"; return result; }
            for (int i = 0; i < parameteres.Length; i++)
            {
                try
                {
                    var paramVal = Convert.ChangeType(parameteres[i], mParameters[i].ParameterType);
                }
                catch (Exception)
                {
                    { result.Error = $"Parameter [{i}]({mParameters[i]})={parameteres[i]} is incompatible"; return result; }
                }
            }


            try
            {
                result.Result = method?.Invoke(calc, parameteres);
            }
            catch (Exception e)
            {
                result.Error = e.Message;
            }
            return result;
        }
    }

    public class CalculatorImpl
    {
        public int Add(int p1, int p2)
        {
            return p1 + p2;
        }

        public string Print(string text, int n1)
        {
            return $"{text}: {n1}";
        }

        public int AddPair(Pair p)
        {
            return p.V1 + p.V2;
        }
    }
}

Client: 客户:

class Program
{
    static void Main(string[] args)
    {
        var calc = new CalculatorServiceClient();
        var result = calc.GetResult(Operation.Add, new object[] { 2, 3 });
        if (string.IsNullOrEmpty(result.Error))
            Console.WriteLine(result.Result);
        else
            Console.WriteLine(result.Error);

        result = calc.GetResult(Operation.Print, new object[] { "result", result.Result });
        if (string.IsNullOrEmpty(result.Error))
            Console.WriteLine(result.Result);
        else
            Console.WriteLine(result.Error);

        result = calc.GetResult(Operation.Add, new object[] { 2, "c3" });
        if (string.IsNullOrEmpty(result.Error))
            Console.WriteLine(result.Result);
        else
            Console.WriteLine(result.Error);

        result = calc.GetResult(Operation.AddPair, new object[] { new Pair { V1 = 3, V2 = 4 } });
        if (string.IsNullOrEmpty(result.Error))
            Console.WriteLine(result.Result);
        else
            Console.WriteLine(result.Error);

        Console.ReadKey();
    }
}

Output: 输出:

5
result: 5
Parameter [1](Int32 p2)=c3 is incompatible
7
  1. I was gonna mention parameter validation, but then I went ahead and done it, using reflection to validate count and object parameter can be converted. 我会提到参数验证,但后来我继续完成它,使用反射来验证计数和object参数可以转换。
  2. Then I thought of complex objects... Yes, they can be sent as object in the parameter array (and they get validated correctly by the above), but they need to be exposed by the service. 然后我想到了复杂的对象......是的,它们可以作为object发送到参数数组中(并且它们通过上面的方式得到了正确的验证),但它们需要由服务公开。 To include an unused class in the service definition use ServiceKnownType attribute. 要在服务定义中包含未使用的类,请使用ServiceKnownType属性。
  3. Having this kind of service definition opens a whole new level of opportunity (for chaos! :)) You can add values to the end of the Operation enum on the server and not break the client. 拥有这种服务定义会打开一个全新的机会(对于混乱!:))您可以在服务器上的Operation枚举末尾添加值,而不是破坏客户端。 Or use a string for the operation code (and not use complex types as parameters, see 2.) and go wild!!! 或者使用string作为操作代码(而不是使用复杂类型作为参数,参见2.)并且疯狂! Multiple versions of servers negotiating with multiple versions of clients, partial server implementations... become possible, obviously requiring some versioning and discovery logic (on a central service?) 多个版本的服务器与多个版本的客户端协商,部分服务器实现......变得可能,显然需要一些版本控制和发现逻辑(在中央服务上?)

In conclusion: I got a little carried away at 3. above, and what I'm describing there must be the exact opposite of a best practice for WCF services. 总结:我在上面有一点被带走了,我在那里描述的必须与WCF服务的最佳实践完全相反。 If I'm not mistaken, the fact that changing the server breaks the clients is considered one of the advantages of WCF. 如果我没有弄错,更改服务器打破客户端的事实被认为是WCF的优势之一。 But I'd still consider the above solution as valid for some scenarios like 但我仍然认为上述解决方案对某些情况有效

  • quickly wrapping in a service a large library that doesn't change or that the clients don't mind not always getting a response from 快速地在服务中包装一个不会改变的大型库或者客户不介意不总是得到响应

  • allowing for some degree of flexibility when clients are numerous and cannot be updated quickly so different versions need to work in parallel. 当客户众多且无法快速更新时允许一定程度的灵活性,因此不同版本需要并行工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM