简体   繁体   中英

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. This works as a programmatic interface to my app. Now I would like to invoke this library over the network using 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. 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? 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.
  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. To include an unused class in the service definition use ServiceKnownType attribute.
  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. Or use a string for the operation code (and not use complex types as parameters, see 2.) and go wild!!! 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. If I'm not mistaken, the fact that changing the server breaks the clients is considered one of the advantages of 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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