Coffeehouse Thread

41 posts

Forum Read Only

This forum has been made read only by the site admins. No new threads or comments can be added.

.Net versioning question

Back to Forum: Coffeehouse
  • User profile image
    cbae

    @davewill: He said that the method and type names are identical.

  • User profile image
    cbae

    @BitFlipper: BTW, whenever you release a new version of the API, you have to modify each of the 7500 methods with a new "if (version === n)" block anyway, right?

    If so, you can create a more generalized call for the "else" block by using a standard interface for all new versions of the API going forward. That way, you can keep the exist code for all the old versions of the API and have easier route of maintainability for future versions of the API.

  • User profile image
    BitFlipper

    TBH, I don't call all of the 7500 methods, just a subset. However I do make enough calls with enough complex arguments that it is still a daunting task to special-case each and every call I do need to make.

    I took a closer look at the generated proxy classes and the only difference between the versions is that the methods are declared like this:

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:internalversion123/1.0", RequestNamespace="urn:internalversion123", ResponseNamespace="urn:internalversion123", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public void Foo() {...}

    ...vs...

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:internalversion123/2.0", RequestNamespace="urn:internalversion123", ResponseNamespace="urn:internalversion123", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public void Foo() {...}

    Note the only difference is the "1.0" vs "2.0". I wonder if there is a way to change that value after the fact, so that calling the methods would pass in whatever value I set programmatically. This way I can always use the latest WSDL version and just override the version to whatever I want. Seems hacky I know, but remember this is just for an internal testing/debugging tool.

  • User profile image
    cbae

    @BitFlipper: It's all code generated anyway. There's nothing "hacky" about running some post process to generated code. Search and replace is your friend.

  • User profile image
    BitFlipper

    , cbae wrote

    @BitFlipper: It's all code generated anyway. There's nothing "hacky" about running some post process to generated code. Search and replace is your friend.

    But how would search/replace solve my problem? I would still end up with two DDLs and two code paths, no? The problem here is that in one case I need to pass in 1.0 when calling into an older server. In the other case I need to pass in 2.0.

    Ideally I need one DLL and one class (Api vs Api10/Api20) but be able to programmatically change the 1.0/2.0 value that is passed to the server during each call.

  • User profile image
    BitFlipper

    OK I have another idea...

    Use TypeDescriptor.AddProvider to add a custom TypeDescriptionProvider class. Then in TypeDescriptionProvider.CreateInstance, I return an instance of either Api10 or Api20.

    The documentation says "Creates an object that can substitute for another data type". I'm wondering if this means you can return a completely different type that has all of the same methods etc. Seems a bit out there for .Net's strict type checking but worth a try I guess. If it works it will solve my problem nicely.

  • User profile image
    cbae

    What I'm saying is that you could make these proxy classes implement an interface or simply subclass from one another (i.e. make Api20 be a subclass of Api10). You can do this, right? Since you said newer versions of the Api only add new methods, it seems that you could.

    Make all of the methods of the base class virtual by adding the keyword to all method signatures. Then add "override" to subsequent versions using search and replace:

    Search for: "void "

    Replace with: "void override "

    For new methods that are added to the particular Api, make those virtual as well so that they can be overridden in the subclass with different attributes.

    In the methods that call the proxy methods, cast all instances of the Api to the version in which the method first appears, and you can eliminate all of the (version == n) nonsense.

     

     

  • User profile image
    davewill

    @cbae: My confusion.

    , including dealing with different types defined by each different API (even though the method and type names themselves are identical).

    overwrote the earlier bits in my head

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

  • User profile image
    BitFlipper

    @cbae: I could look into this as it could work. However it is going to take some time sorting it out since there are many methods and I should be careful of doing find/replace if it isn't targeted enough.

    I'm just wondering... would the overridden method's attributes replace the base class's method attributes?

    In addition, maybe a modified approach of your suggestion would be to derive a new class from Api20 called Api10, and only implement the subset of methods I need (replacing the SOAP version attribute in those methods with the required 1.0). I can also use "new" instead of "override" meaning I don't have to mark all methods in the base class as virtual. This way I don't have to touch the base class.

  • User profile image
    BitFlipper

    LOL up until now I have created the DLLs from the WSDL using the VS command line tools. Just for kicks I used the "Add Service Reference" in VS to add the WSDL file into an empty project. It was able to generate the *.cs file but is unable to compile it due to an OutOfMemory exception. I opened the *.cs file and it is 1.7+ million lines of code.

    I guess this is one example of where a 64-bit version of VS would come in handy.

  • User profile image
    cbae

    @BitFlipper: By hiding members with "new" you lose the benefit of polymorphism which is the reason for using subclasses in the first place.

    Perhaps you can create a custom T4 template to generate the initial version of the API as an interface: IApiV1.

    Then for each subsequent version create another interface: IApiV2, IApiV3, etc. with only the new methods for that particular version, which should be easy to do even if you did it manually.

    Your class declarations would then be like:

    public class ApiV1: IApiV1{}

    public class ApiV2: IApiV1, IApiV2 {}

    public class ApiV3: IApiV1, IApiV2, IApiV3 {}

    For each generated class, you'd  need only change the class declaration.

    In the calling program you simply cast the Api instance to the interface that you know has the method declaration. If the version is lower than what is expected for the method in question, then you throw an exception or just don't call the Api method.

  • User profile image
    cbae

    Another thing is that using interfaces has some interesting implications. Consider this code:

            public interface IV1
            {
                void SomeMethod1();
            }
            public interface IV2
            {
                void SomeMethod1();
                void SomeMethod2();
            }
            public interface IV3
            {
                void SomeMethod1();
                void SomeMethod2();
                void SomeMethod3();
            }
    
            public class V1 : IV1
            {
                //Implements IV2
                public void SomeMethod1()
                {
                    Console.WriteLine("V1.SomeMethod1");
                }
            }
    
            public class V2 : IV1, IV2
            {
                //Implements IV1, IV2
                public void SomeMethod1()
                {
                    Console.WriteLine("V2.SomeMethod1");
                }
    
                //Implements IV2
                public void SomeMethod2()
                {
                    Console.WriteLine("V2.SomeMethod2");
                }
            }
    
            public class V3 : IV1, IV2, IV3
            {
                //Implements IV1, IV2, and IV3
                public void SomeMethod1()
                {
                    Console.WriteLine("V3.SomeMethod1");
                }
    
                //Implements IV2 and IV3
                public void SomeMethod2()
                {
                    Console.WriteLine("V3.SomeMethod2");
                }
    
                //Implements IV3
                public void SomeMethod3()
                {
                    Console.WriteLine("V3.SomeMethod3");
                }
            }
    
    
            static void Main(string[] args)
            {
                var x = new V3();
                var xAsIV11 = (IV1)x;
                xAsIV11.SomeMethod1();  //V3.SomeMethod1
    
                var xAsIV2 = (IV2)x;
                xAsIV2.SomeMethod2();  //V3.SomeMethod2
                xAsIV2.SomeMethod1();  //V3.SomeMethod1
    
                var xAsIV3 = (IV3)x;
                xAsIV3.SomeMethod3(); //V3.SomeMethod3
                xAsIV3.SomeMethod2(); //V3.SomeMethod2
                xAsIV3.SomeMethod1(); //V3.SomeMethod1
            }

    This means that if you use a custom T4 template to create the interface definitions for each Api version, you can include all the methods for a given version and not worry about whether or not the method is defined in an older version of the Api.

     

  • User profile image
    BitFlipper

    @cbae: No I think it should work. For one thing I just checked and using "new" does return the derived class's method attributes, meaning I can specify an alternate version #. Also, I'm not sure why you say I will lose polymorphism because for this case it doesn't matter, right?

    So if I had:

    public class Api20
    {
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:internalversion123/2.0", ...)]
        public void Foo(...)
        {
            this.Invoke("Foo", new object[] {...});
        }
    }
    
    public class Api10 : Api20
    {
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:internalversion123/1.0", ...)]
        public new void Foo(...)
        {
            this.Invoke("Foo", new object[] {...});
        }
    }
    
    ...
    
        Api20 api;
    
        if (version == 1)
            api = new Api10();
        else
            api = new Api20();
    
        api.Foo(...);

  • User profile image
    cbae

    @BitFlipper: I thought the point was to eliminate the if statements in all of the methods. Consider this code:

        class Program
        {
            public interface IV1
            {
                void SomeMethod1();
            }
    
            public interface IV2
            {
                void SomeMethod1();
                void SomeMethod2();
            }
            public interface IV3
            {
                void SomeMethod1();
                void SomeMethod2();
                void SomeMethod3();
            }
    
            public abstract class V0 { }
    
            public class V1 : V0, IV1
            {
                //Implements IV2
                public void SomeMethod1()
                {
                    Console.WriteLine("V1.SomeMethod1");
                }
            }
    
            public class V2 : V0, IV1, IV2
            {
                //Implements IV1, IV2
                public void SomeMethod1()
                {
                    Console.WriteLine("V2.SomeMethod1");
                }
    
                //Implements IV2
                public void SomeMethod2()
                {
                    Console.WriteLine("V2.SomeMethod2");
                }
            }
    
            public class V3 : V0, IV1, IV2, IV3
            {
                //Implements IV1, IV2, and IV3
                public void SomeMethod1()
                {
                    Console.WriteLine("V3.SomeMethod1");
                }
    
                //Implements IV2 and IV3
                public void SomeMethod2()
                {
                    Console.WriteLine("V3.SomeMethod2");
                }
    
                //Implements IV3
                public void SomeMethod3()
                {
                    Console.WriteLine("V3.SomeMethod3");
                }
            }
    
            private static V0 GetApi(int version)
            {
                V0 api;
                switch (version)
                {
                    case 1:
                        api = new V1();
                        break;
                    case 2:
                        api = new V2();
                        break;
                    case 3:
                    default:
                        api = new V3();
                        break;
                }
                return api;
            }
    
            static void Main(string[] args)
            {
                DoMethod1(3); //V3.SomeMethod1
                DoMethod1(2); //V2.SomeMethod1
                DoMethod1(1); //V1.SomeMethod1
    
                DoMethod2(3); //V3.SomeMethod2
                DoMethod2(2); //V2.SomeMethod2
                DoMethod2(1); //This Api version doesn't support this feature!
    
                DoMethod3(3); //V3.SomeMethod3
                DoMethod3(2); //This Api version doesn't support this feature!
                DoMethod3(1); //This Api version doesn't support this feature!
            }
    
            static void DoMethod1(int version)
            {
                var api = (IV1)GetApi(version);
                api.SomeMethod1();
            }
    
            static void DoMethod2(int version)
            {
                if (version >= 2)
                {
                    var api = (IV2)GetApi(version);
                    api.SomeMethod2();
                }
                else
                {
                    Console.WriteLine("This Api version doesn't support this feature!");
                }
            }
    
            static void DoMethod3(int version)
            {
                if (version >= 3)
                {
                    var api = (IV3)GetApi(version);
                    api.SomeMethod3();
                }
                else
                {
                    Console.WriteLine("This Api version doesn't support this feature!");
                }
            }
    
        }

     

    This code is future proof. Even if I create a new version of the Api (i.e. class V4), DoMethod1, DoMethod2, and DoMethod3 can remain untouched even though different versions of the Api can be used. The only thing these methods need to be aware of is what version of the Api a particular feature first appeared in. That's it. Once you account for that, any new version will work with the code without ever having to add another if block.

  • User profile image
    BitFlipper

    @cbae: The problem is that I think your approach adds a lot of required coding. With my approach I can leave the Api20 class untouched and simply derive Api10 from Api20, then create new versions of only the methods I need. The methods in Api10 themselves are simply cut/pasted from the Api20 version and the "2.0" in the attribute is changed to "1.0". That is all.

    Also, there is one and only one if statement in my code: Where the Api class gets instantiated. There is no if statement required anywhere else.

  • User profile image
    cbae

    @BitFlipper: I thought you had issues with code duplication as a result of the Api versions being of different types. Using interfaces allows you to use different concrete types but have the code generalized because you program to a common interface instead of the concrete type.

  • User profile image
    BitFlipper

    @cbae: Well when I say code duplication, I mean where the API is used (the consumer of the API), not so much inside the wrapper/subclass or whatever.

    Your solution is not bad at all (and in an officially released product might be the better choice). I just think the last solution I mention would most likely result in less coding required. I don't need to pass any version info etc (so I can keep the exact same method signature), and I don't have to do anything to the Api10 methods other than simply copy/pasting each one and tweaking one of its attributes.

  • User profile image
    BitFlipper

    So as it turns out my solution won't work because the derived class (containing the "new" methods), cannot be instantiated. There is an exception being thrown at class instantiation time saying that the "new" method cannot be reflected and that method name should be changed (due to conflict with the base class I'm sure). I believe the web services functionality can't deal with a class containing a "new" method that hides the base class method. I have not tried marking the base class methods as virtual yet and simply overriding the methods in the derived class.

    @cbae: I took another look at your suggestion but it just isn't a functional solution in this case. The reason is because it requires manual changes to the original proxy class, and every time I re-generate it from new WSDL, those changes will be blown away. While the actual version # doesn't change very often, I still need to re-generate the proxy class relatively often to keep up with the changing server side (the server side is developed by our company).

    So I need a solution that doesn't need manual changes to the proxy class.

    I have another possible solution... Due to the sheer size of the proxy class, I already need to perform other parsing operations on the generated proxy class. The main thing I need to do in this case is to generate an external serializer DLL (using wsdl.exe, csc.exe and , ngen.exe) because if I don't do that, it literally takes minutes to instantiate the proxy class. I have already automated this process.

    Since I really only need a subset of the methods in the proxy class, maybe a solution could be to create a text file that lists only the methods I actually need to use (roughly 50 out of 750). Then change the parser to read that text file and create a new proxy.cs file that only contains those methods (and with some smart parsing, only the types used by those listed methods). This will cut down dramatically on the size of the proxy class, which will also most likely remove the need for the external serializer DLL. Then as part of the automation parsing process, I can create an interface that both classes use which will define all the required methods. And there will be a unique class for each required version. In that regard it will be similar to cbae's suggestion.

    Note that I won't need to do any version checking at all in the methods since I use the exact same set of methods for all versions, and the methods themselves are identical in all versions. The only thing that changes between versions is that new methods and types gets added (which I don't need to use), and that when calling a specific server version, the method needs to contain the correct version string in its SoapDocumentMethodAttribute.

    As said before, I'm not a web services expert but I would have thought there were better solutions to these kinds of versioning problems. I mean, how does a client app usually deal with connecting to different servers that require different versions of an API?

     

     

Conversation locked

This conversation has been locked by the site admins. No new comments can be made.