Coffeehouse Thread

41 posts

.Net versioning question

Back to Forum: Coffeehouse
  • BitFlipper

    I'm not a web services expert so I have the following question...

    Let's say I have a web service that exposes its API via WSDL. Also say I created proxy classes in a DLL from that WSDL that now allows me to interact with the web service. Next, a new version of the web service is released and hence I need to generate a new DLL from that WSDL. None of the previous methods or any of the previous types have changed, only new methods and types were added.

    Here is the problem... My client application needs to be able to connect to either version of the web service, because some servers will still be using the older version. More importantly, it needs to be able to connect to one, disconnect, and then reconnect to a different version.

    So what I'm doing right now is I put the proxy classes for each version inside its own namespace. So I have a namespace SomeApi10, and another one SomeApi20. Then whenever I need to call into the web service, I do either

    if (version == 1)
        InstanceOfSomeApi10.DoSomething();
    else
        InstanceOfSomeApi20.DoSomething();

    This is not ideal because in most cases the code is more complex and I need to duplicate large blocks of code to handle multiple calls to the correct API version, including dealing with different types defined by each different API (even though the method and type names themselves are identical).

    What I'm looking for is a way to prevent this code duplication (and future-proof the code so that the next version doesn't require me to create yet another code path). I want to be able to put both proxy versions into the same namespace, but have some other way to specifiy which DLL version should be used. I know I can subscribe to the "Resolve" events but this will only help me on during the 1st load, as it is not possible (or very difficult) to unload a DLL again.

    BTW, these APIs are exceedingly complex, with over 7500 calls, and a huge amount of types. It is not possible to simply wrap it with another class (which would still require the multiple code paths internally).

    Any suggestions?

  • cbae

    Are you using static methods? If so, you're making it very tough to add an abstraction layer to account for different versions using dependency injection.

  • davewill

    Sounds like the web service api is backward compatible so why aren't all the servers updated with it?  This would avoid the whole client problem wouldn't it?

  • BitFlipper

    @cbae: Sorry, I updated my post to show that I'm not using static methods.

    @davewill: I don't have control over this. These are large environments with large number of servers, and they are not upgraded at the same time.

  • Sven Groot

    Does the old version proxy not work against the new version service (for those methods that the two versions share). If so, why not? Or vice versa, for that matter. It sounds like you should be able to just use the new proxy DLL and make sure you don't call the new methods for servers that use the old API.

  • vesuvius

    , BitFlipper wrote

    None of the previous methods or any of the previous types have changed, only new methods and types were added.

     

    Any suggestions?

    Use IExtensibleDataObject

     

  • BitFlipper

    One idea... Calling an older server with the newer API results in a version mismatch exception. I'm wondering if I could hack the proxy class such that it returns whatever version I specify, which would then allow me to use the latest version in all cases.

    Note this application I'm writing is a debugging tool for internal use, it would never be released so hacks like these are fine IMHO. I'm just not sure whether it is possible though.

     

  • BitFlipper

    I looked at that but it seems it only solves the case where an object is extended in future versions. In my particular case, the objects are not extended, instead there are new objects and new methods. Also as mentioned above, the problem is that I can't connect to an older server due to a version mismatch check that is performed somewhere in the proxy class.

    Also note I have no control over how the server API is implemented.

    , Sven Groot wrote

    Does the old version proxy not work against the new version service (for those methods that the two versions share). If so, why not? Or vice versa, for that matter. It sounds like you should be able to just use the new proxy DLL and make sure you don't call the new methods for servers that use the old API.

    I get a version mismatch exception from the server if I try to use the newer version with an older server.

  • BitFlipper

    Another possible solution that is "cleaner" but not ideal...

    Right now the login code is part of the main application. What I could possibly do is create a stand-alone "launcher" application. When logging into a specific server, the launcher determines what version the server is and launches the main application, specifying the required version in the command line args. The main app doesn't add the proxy DLLs as references (however it won't compile then I believe - how to do this?), instead subscribes to the Resolve events and specified which DLL to use based on the passed in version info.

    When the user clicks on "Logout", the main app launches the launcher app again and exits. However at that point I might as well just make the main app only be able to connect to one version, and if connecting to a new version, simply exits and launches a new instance of itself.

    I guess there are many ways to skin this cat... [what a horrible saying...]

  • cbae

    @BitFlipper: Can you create wrapper classes (deriving from the same interface) that calls specific versions of the API? Then put each instance of these wrapper classes into a Dictionary keyed by the version number. Your calling routine can be generalized to simply look up the instance by version number in the Dictionary and then call the appropriate method exposed by the interface on that particular instance.

    The routine that populates the Dictionary can read the types from configuration and instantiate them dynamically.

  • BitFlipper

    @cbae: I don't think this could work because what about the types? While they are identical in each case, due to .Net being strictly typed, Api10.ClassA is not the same as Api20.ClassA.

  • cbae

    , BitFlipper wrote

    @cbae: I don't think this could work because what about the types? While they are identical in each case, due to .Net being strictly typed, Api10.ClassA is not the same as Api20.ClassA.

    That's why you have wrapper classes. Each wrapper class knows which specific API method to call. You use the wrapper classes to give you a common interface method to call in order to generalize the routine that does the Dictionary lookups.

    That is if you don't want to simply change each of the Api classes to implement a common interface to begin with.

  • BitFlipper

    @cbae: Sorry I'm not following. So how would I do this...

    var foo = new ApiXX.Foo(); //???
    foo.Value1 = 1;
    foo.Value2 = 2.
    
    ApiInterfaceInstance.DoSomething(foo); // This makes sense

  • cbae

    @BitFlipper: I'm talking about something like this:

    public interface IApiWrapper
    {
        void DoSomething();
    }
    
    public class Api10
    {
        public void DoSomething()
        {
            //Do API-specific stuff here
        }
    }
    
    public class Api20
    {
        public void DoSomething()
        {
            //Do API-specific stuff here
        }
    }
    
    public class Api20Wrapper : IApiWrapper
    {
        public void DoSomething()
        {
            var api = new Api20();
            api.DoSomething();
        }
    }
    
    public class Api10Wrapper : IApiWrapper
    {
        public void DoSomething()
        {
            var api = new Api10();
            api.DoSomething();
        }
    }
    
    public class ClientApplication
    {
        private Dictionary<int, IApiWrapper> _Wrappers = new Dictionary<int, IApiWrapper>();
        private void Initialize()
        {
            _Wrappers.Add(1, new Api10Wrapper());
            _Wrappers.Add(2, new Api20Wrapper());
        }
    
        public void CallApi(int version)
        {
            var wrapper = _Wrappers[version];
            wrapper.DoSomething();
        }
    }

    Obviously, you won't be able to just new-up the instances in the Initialize() method if you want to read from configuration, but you get the idea.

  • BitFlipper

    I had a different project where I had the "same" class defined in two different projects in different namespaces. When I serialized the class in project A, I got a serialization exception when deserializing in project B (as expected). However by subscribing to AppDomain.AssemblyResolve, I was able to simply pass in the currently executing assembly and the class successfully deserialized even though it was in a different namespace.

    I wonder if I can take advantage of that in some way by passing in the correct DLL version in OnAssemblyResolve, but it would require me to "unregister" the class again when switching servers so that I get asked again where to locate the class.

  • BitFlipper

    @cbae: That works in theory but in practice is not possible due to there being 7500 methods as stated earlier. There is no way I can manually code that up. Also each call is quite a bit more involved than your example, with many external variables needed, so even just one wrapped method would be quite complex with many required arguments. At that point I might as well just stick with the original solution as it will actually be a bit less code overall (yet still way too much code duplication to be maintainable).

  • cbae

    , BitFlipper wrote

    @cbae: That works in theory but in practice is not possible due to there being 7500 methods as stated earlier. There is no way I can manually code that up. Also each call is quite a bit more involved than your example, with many external variables needed, so even just one wrapped method would be quite complex with many required arguments. At that point I might as well just stick with the original solution as it will actually be a bit less code overall (yet still way too much code duplication to be maintainable).

    I also said:

    That is if you don't want to simply change each of the Api classes to implement a common interface to begin with.

    If you don't want the extra wrapper you can make each of the Api classes implement the same interface.

    You don't have to do anything to the methods of the concrete classes. You need only change the class declaration, and all the existing methods that have the same signature as defined in the interface will automatically implement the interface.

    However, you will need to create the interface with all of the 7500 method signatures. That's the bullet you'll have to bite, but creating the interface would be just like creating a new Api class without having any code.

  • davewill

    It is sounding more like your faced with a bigger problem.  To expand your original example, something like this

    if (version == 1)
        AArray=InstanceOfSomeApi10.GetSomething(AnInt,AString);
        AObjectModelCollection=new ObjectModel.Collection(AArray)
    else
        AObjectModelCollection = InstanceOfSomeApi20.GetSomething(ADouble,AInt,AString);
    
    //Do stuff with AObjectModelCollection in common code from this point forward.

     

    I.E. the web service method call isn't just an interchangable thing.  Not sure how to do better than what you already are.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.