views:

981

answers:

4

I've started writing an interface for FedEx's webservice APIs. They have 3 different APIs that I'm interested in; Rate, Ship, and Track. I am generating the service proxies with SvcUtil.exe.

The different service endpoints are each specified by FedEx in their own WSDL files. Each service endpoint has it's own xml namespace (e.g. http://fedex.com/ws/rate/v5 and http://fedex.com/ws/ship/v5)

The service endpoints do use quite a few identical types such as Address, Measurements, Weight, AuthenticationDetail, ClientDetail, etc...

And here is where the problem lies, I can provide all the WSDL files at the same time to SvcUtil.exe and normally it would coalesce any identical types into a single shared type, but since each of FedEx's services are in their own namespace, and they redeclare these types in each WSDL file under that namespace what I end up with instead is an Address, Address1, and Address2 one for each namespace.

To solve that issue, what I do now is to run each WSDL through svcutil separately and put them each in their own .NET namespace (e.g. FedEx.Rate, FedEx.Ship, FedEx.Track). The problem with this is that now I have a distinct address type in each namespace (Fedex.Rate.Address, FedEx.Ship.Address).

This makes it difficult to generalize the code used between the services like a GetAuthenticationDetail() factory method so I don't have to repeat that code in every place I use the different services.

Is there any way in C# to Coerce FedEx.Rate.Address to FedEx.Ship.Address?

+5  A: 

If the types are identical, and you have control over the source classes, you can define a conversion operator in the class, and any function that takes a Rate.Address will also automatically take a Ship.Address. For example:

namespace Rate {
    class Address {
        string Street;
        string City;
        // ...

        public static implicit operator Ship.Address(Rate.Address addr) {
            Ship.Address ret;
            ret.Street = addr.Street;
            ret.City = addr.City;
            // ...

            return ret;
        }
    }
}

My C# is a little rusty but I hope you get the idea.

zildjohn01
There is a problem with this approach. For example if the WSDL changes (some property is added) and the proxies are regenerated with svcutil.exe you don't have to forget updating the implicit oprator method or you might get some strange behavior at runtime.
Darin Dimitrov
hmm, I like this approach, and I even read about conversion operators in C# a couple weeks ago. Let me give it a try. The one nice thing about FedEx's webservices is that when they release a new version the old still works indefinitely. So darin's comment isn't going to cause me too many problems
joshperry
Maybe I'll look at implementing the body of the operator using reflection, then if the WSDL does change it should just automatically work as long as you have the correct conversion operators in place.
joshperry
@joshperry, this looks like a good solution for projects that could tolerate the reflection overhead.
Darin Dimitrov
I accepted your answer because it gave me all that I needed to solve my problem! Check my answer to this question for the code I used in the implicit conversion operator. It uses reflection to copy the objects. I'll implement a couple conversions explicitly to compare performance.
joshperry
Purely as a matter of style I would suggest making it an explicit operator instead. That way, the next developer who reads your code won't be potentially fooled into thinking that the two types are the same.
Christian Hayter
A: 

you could use operator overloading by creating your own implementation of Address or use one of the stable types as a property

one example: Address1 and Address2 below would be your Rate.Address and Ship.Address respectively

class Address1
{
    public string name = "Address1";
}
class Address2
{
    public string name = "Address2";
}

class GenericAddress
{
    public string name = "GenericAddress";
    public static implicit operator GenericAddress(Address1 a)
    {
        GenericAddress p = new GenericAddress(); p.name = a.name; return p;
    }
    public static implicit operator GenericAddress(Address2 a)
    {
        GenericAddress p = new GenericAddress(); p.name = a.name; return p;
    }
}
class Program
{
    static void Main(string[] args)
    {
        PrintName(new Address1());//prints address1
        PrintName(new Address2());//prints address2
    }

    static void PrintName(GenericAddress a)
    {
        Console.WriteLine(a.name);
    }
}

Edit: the approach is the same as the post above, the implementation is in a seperate class thats all

This isn't a bad idea except that it is alot of work to duplicate all of the types that already exist, I have contemplated writing a code generator that would do something similar to this, but custom Visual Studio code generators are hard to implement in a team environment.
joshperry
+1  A: 

Are those generated classes defined as "partial" ? If so, you could extend them in a different file and extract an interface and let it be implemented by all Address classes.

flq
This will let you treat the types uniformly in your code, but this won't let you pass the FedEx.Rate.Address to the FedEx.Ship service.
David B
+4  A: 

So here is how I implemented the implicit conversion operators using reflection. SvcUtil creates partial classes so I added an implicit conversion operator for each direction of the conversion so in the client code you can just type Type1 = Type2.

In this snippet WebAuthenticationCredentials is a property of WebAuthenticationDetails so while iterating the properties of the source object if the types arent the same (built-ins) it checks the name of the types (without the namespace) and recursively calls the copy function with those properties.

internal class ReflectionCopy
{
    public static ToType Copy<ToType>(object from) where ToType : new()
    {
        return (ToType)Copy(typeof(ToType), from);
    }

    public static object Copy(Type totype, object from)
    {
        object to = Activator.CreateInstance(totype);

        PropertyInfo[] tpis = totype.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        PropertyInfo[] fpis = from.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

        // Go through each property on the "to" object
        Array.ForEach(tpis, tpi =>
        {
            // Find a matching property by name on the "from" object
            PropertyInfo fpi = Array.Find(fpis, pi => pi.Name == tpi.Name);
            if (fpi != null)
            {
                // Do the source and destination have identical types (built-ins)?
                if (fpi.PropertyType == tpi.PropertyType)
                {
                    // Transfer the value
                    tpi.SetValue(to, fpi.GetValue(from, null), null);
                }
                else
                {
                    // If type names are the same (ignoring namespace) copy them recursively
                    if (fpi.PropertyType.Name == tpi.PropertyType.Name)
                        tpi.SetValue(to, Copy(fpi.PropertyType, tpi.GetValue(from, null)), null);
                }
            }
        });

        return to;
    }
}

namespace Rate
{
    partial class WebAuthenticationDetail
    {
        public static implicit operator Ship.WebAuthenticationDetail(WebAuthenticationDetail from)
        {
            return ReflectionCopy.Copy<Ship.WebAuthenticationDetail>(from);
        }
    }

    partial class WebAuthenticationCredential
    {
        public static implicit operator Ship.WebAuthenticationCredential(WebAuthenticationCredential from)
        {
            return ReflectionCopy.Copy<Ship.WebAuthenticationCredential>(from);
        }
    }
}

namespace Ship
{
    partial class WebAuthenticationDetail
    {
        public static implicit operator Rate.WebAuthenticationDetail(WebAuthenticationDetail from)
        {
            return ReflectionCopy.Copy<Rate.WebAuthenticationDetail>(from);
        }
    }

    partial class WebAuthenticationCredential
    {
        public static implicit operator Rate.WebAuthenticationCredential(WebAuthenticationCredential from)
        {
            return ReflectionCopy.Copy<Rate.WebAuthenticationCredential>(from);
        }
    }
}
joshperry