Comparing Two Bindings

Too often I have to compare two bindings to find the subtle difference between a configuration that works and a configuration that doesn't. This is quite a painful task as a channel stack can be generated on the fly based on different settings, there are dozens of configuration options (for security alone), and the difference may be buried in deeply nested objects.

For simple jobs I wrote programs to try to do the comparison for me. In general this is a silly task because a small configuration change can alter the binding greatly. However, if you're comparing bindings, it's probably because you already know they're very nearly identical. Each time I ran into a problem I tweaked the comparison tool a bit but this week I decided to clean up the effort. It's still silly though.

If you ever need to compare two bindings, this will probably not help but may be fun to try.

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;

public class Diff
{
    public string name;
    public object o1;
    public object o2;

    public static IEnumerable<Diff> Compare(Binding b1, Binding b2)
    {
        IEnumerable<Diff> q;
        return
            PairBindingElements(b1, b2).
            Where(pair => !IsEqual(pair.Item1, pair.Item2)).
            SelectMany(pair => Compare(new Diff(null, pair.Item1, pair.Item2)));
    }

    static bool IsEqual(BindingElement be1, BindingElement be2)
    {
        BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance;
        return (bool)typeof(BindingElement).InvokeMember("IsMatch", flags, null, be1, new object[] { be2 });
    }

    static IEnumerable<Diff> Compare(Diff d)
    {
        if (d.o1 == null || d.o2 == null)
        {
            if (d.o1 != null || d.o2 != null)
            {
                yield return d;
            }
            yield break;
        }
        if (d.o1.GetType() != d.o2.GetType())
        {
            yield return new Diff(d.name ?? string.Format("{0}/{1}", d.o1.GetType(), d.o2.GetType()), d.o1, d.o2);
            yield break;
        }
        d.name = d.name ?? d.o1.GetType().Name;
        if (d.o1.GetType().IsPrimitive || d.o1.GetType() == typeof(string))
        {
            if (!d.o1.Equals(d.o2))
            {
                yield return d;
            }
            yield break;
        }
        BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
        IEnumerable<Diff> differences =
            d.o1.GetType().GetProperties(flags).
            Where(info => info.GetIndexParameters().Length == 0).
            Select(info => new Diff(d.name + "." + info.Name, info.GetValue(d.o1, null), info.GetValue(d.o2, null))).
            SelectMany(difference => Compare(difference));
        foreach (Diff difference in differences)
        {
            yield return difference;
        }
    }

    static IEnumerable<Tuple<BindingElement, BindingElement>> PairBindingElements(Binding b1, Binding b2)
    {
        BindingElementCollection bec1 = b1.CreateBindingElements();
        BindingElementCollection bec2 = b2.CreateBindingElements();
        while (bec1.Count != bec2.Count)
        {
            (bec1.Count < bec2.Count ? bec1 : bec2).Insert(0, new DummyBindingElement());
        }
        return bec1.Zip(bec2, (first, second) => new Tuple<BindingElement, BindingElement>(first, second));
    }

    public Diff(string name, object o1, object o2)
    {
        this.name = name;
        this.o1 = o1;
        this.o2 = o2;
    }

    public override string ToString()
    {
        return string.Format("{0} on binding 1\n{1}\n{0} on binding 2\n{2}\n", name, o1 ?? "(null)", o2 ?? "(null)");
    }
}

class DummyBindingElement : BindingElement
{
    public override BindingElement Clone() { throw new NotImplementedException(); }
    public override T GetProperty<T>(BindingContext context) { throw new NotImplementedException(); }
}

class Program
{
    static void Main(string[] args)
    {
        var b1 = new NetTcpBinding();
        b1.Security.Mode = SecurityMode.Message;
        var b2 = new NetTcpBinding();
        b2.TransactionProtocol = TransactionProtocol.WSAtomicTransaction11;
        b2.PortSharingEnabled = true;
        b2.MaxReceivedMessageSize = 32000;
        b2.Security.Mode = SecurityMode.Message;
        b2.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
        foreach (Diff difference in Diff.Compare(b1, b2))
        {
            Console.WriteLine("{0}", difference);
        }
    }
}

Running it with the test bindings spots these differences:

 TransactionFlowBindingElement.TransactionProtocol on binding 1
System.ServiceModel.OleTransactionsProtocol
TransactionFlowBindingElement.TransactionProtocol on binding 2
System.ServiceModel.WSAtomicTransaction11Protocol
 SymmetricSecurityBindingElement.ProtectionTokenParameters.BootstrapSecurityBindingElement.ProtectionTokenParameters on binding 1
System.ServiceModel.Security.Tokens.SspiSecurityTokenParameters:
InclusionMode: AlwaysToRecipient
ReferenceStyle: Internal
RequireDerivedKeys: True
RequireCancellation: True
SymmetricSecurityBindingElement.ProtectionTokenParameters.BootstrapSecurityBindingElement.ProtectionTokenParameters on binding 2
System.ServiceModel.Security.Tokens.SslSecurityTokenParameters:
InclusionMode: AlwaysToRecipient
ReferenceStyle: Internal
RequireDerivedKeys: True
RequireCancellation: True
RequireClientCertificate: True
 TcpTransportBindingElement.PortSharingEnabled on binding 1
False
TcpTransportBindingElement.PortSharingEnabled on binding 2
True
 TcpTransportBindingElement.MaxBufferSize on binding 1
65536
TcpTransportBindingElement.MaxBufferSize on binding 2
32000
 TcpTransportBindingElement.MaxReceivedMessageSize on binding 1
65536
TcpTransportBindingElement.MaxReceivedMessageSize on binding 2
32000