• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1005
  • Last Modified:

Problem on web services

Hello:

I have here a problem where I am using web services.

From this example you can see that webservices is very tolerant of extra fields or missing fields.  One issue though is that if you run one of the webservices and then update the other project's web reference it generates partial class code for your remote object in the namespace of the webservice.  This is a problem.

For instance:

If you run the solution Version1 and then open the solution

Version2 and update the Web Reference in the project (you have to show hidden files) you get a file called Reference.cs.

 In Reference.cs the system generates a wrapper for the class Foo

   /// <remarks/>
   [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml","2.0.50727.1378")]
   [System.SerializableAttribute()]
   [System.Diagnostics.DebuggerStepThroughAttribute()]
   [System.ComponentModel.DesignerCategoryAttribute("code")]
   [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]

    public partial class Foo {...}

 The problem is that when it does this it will return this object as when you call HelloWorld.

         static void Main(string[] args)
        {
            ConsoleApplication1.localhost.Service1 s1 = new Service1();
            MyLibrary.Foo f1 = new MyLibrary.Foo();
            f1.Prop1 = "New";
            MyLibrary.Foo f2 = (MyLibrary.Foo)s1.HelloWorld( f1);
            System.Console.WriteLine("F1.Prop1=" + f2.Prop1);
        }

Which is not what you want.  The intended object is MyLibrary.Foo not ConsoleApplication1.localhost.Foo.  Please figure out how prevent VS from creating the wrapper or any other solution.

Below are the project codes:
"Version1" Solution:
Webservices:
using System;
using System.Data;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Xml;
using System.Xml.Serialization;

using MyLibrary.Lib;

namespace Version1
{  
    /// <summary>
    /// Summary description for Service1
    /// </summary>
    [WebService(Namespace = "http://localhost/")]  
    [ToolboxItem(false)]  
    public class Service1 : System.Web.Services.WebService
    {
        [WebMethod(EnableSession = true)]
        [XmlInclude(typeof(Foo))]
        public MyLibrary.Lib.FooHelloWorld(MyLibrary.Lib.Foo foo1)      
        {
            foo1 = "Updated";          
            return foo1;          
        }
    }
}

"MyLibrary.cs"

using System;
using System.Data;
using System.Configuration;
using System.Xml;
using System.Xml.Serialization;

namespace MyLibrary
{  
    public class Foo
    {      
        private string m_prop1 = null;
        public string Prop1
        {
            get { return m_prop1; }
            set { m_prop1 = value; }
        }
     
        private string m_prop2 = null;
        public string Prop2
        {
            get { return m_prop2; }
            set { m_prop2 = value; }
        }

        public Foo()
        {
            m_prop1 = "abc";
            m_prop2 = "cde";
        }
    }
}

Console application: "Program.cs"
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

using MyLibrary.Lib;
namespace ConsoleApplication1
{
         static void Main(string[] args)
        {
            ConsoleApplication1.localhost.Service1 s1 = new Service1();
            MyLibrary.Foo f1 = new MyLibrary.Foo();
            f1.Prop1 = "New";
            MyLibrary.Foo f2 = (MyLibrary.Foo)s1.HelloWorld( f1);
            System.Console.WriteLine("F1.Prop1=" + f2.Prop1);
        }
}

*** NOte ***  localhost is the name of the web services when i add reference.
If you can provide solution in this Version1 solution it will work also on Version2 solution.

Hope anyone can save me from hell... Thanks
0
sirknight1919
Asked:
sirknight1919
  • 18
  • 11
  • 5
2 Solutions
 
surajgupthaCommented:
What is returned by the webservice will always be localhost.Foo and not the Class Library.Foo
You will have to either serialize/ deserialize it into ClassLibrary.Foo or use LocalHost.Foo in your form
0
 
drichardsCommented:
Adding a web service reference like that imports the service (including types) and generates the code to call the seb service.  You can either:

1) Just delete the type code after the import since you are using the library version or
2) Hand code the wrapper to call the web service and don't add a reference to the web service project.

That works as long as you have used the same type library in the service code.

I usually start by creating the WSDL and then generate both the client and server code using the WSDL tool and extract the types into a library.  Then you don't have to mess with adding the reference in your project and you can generate new services with the same set of types if you want.
0
 
sirknight1919Author Commented:
surajguptha,
Can you provide code example on how to do it serialize/deserialize? We cannot use localhost.Foo in our forms for many reasons.

Don't hesitate to ask me for more details.

thanks,

0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
sirknight1919Author Commented:
drichards:

Certainly we cannot do the option 1. Option 2 can work but we want to maintain tightly coupled client and server. I read some suggestions that we can minimize this by way of refactoring the class. I still don't know how to do this. here is the excerpt in that forum:

"The OP should realize that bugs in SomeNameSpace.MyComplexClass will require that both client and server be rebuilt and redistributed. The OP can minimize this by refactoring the class and 1) separating common logic and 2) separating the data representation from the business logic (for example: MyComplexClassData and MyComplexClassCommonLogic, MyComplexClassServerLogic, MyComplexClassClientLogic). Refactoring along these lines is more consistent with the Patterns and Practices guidance in the Web Service Software Factory"

On how to do this, I still dont know.

If this is what you were trying to say in your last paragraph, please elaborate and provide codes.

Don't hesitate to ask me for more details.

Thanks.
0
 
drichardsCommented:
#1 works just fine.  All you do is delete the generated local generated classes and put in a "using" declaration for the library namespace from which you want to pull the types instead.  It's a bit of bookkeeping each time you update the reference, but it does work.

Really, though, you should generate the WSDL first and then create the server and client stubs from it.  Then you can just move the server and client code into their respective projects and the types into a separate library project used by both the client and server.
0
 
drichardsCommented:
A bit of bad grammar there.  What I mean to say is delete the local classes generated by the reference operation.  The types used by the web service are generated as classes inside the namespace created by the reference operation.  They will have the same names and layouts as the ones used by the server which are the library types.  You can remove these local classes and just use the ones from the library.

Here'a an example.  I created a web service using class TestType from a library called ClassLibrary1 (ClassLibrary1.TestType).  Then I created another project and added a reference to the web service and got this file:

namespace CSConsole2.localhost {
    using System.Diagnostics;
    using System.Web.Services;
    using System.ComponentModel;
    using System.Web.Services.Protocols;
    using System;
    using System.Xml.Serialization;
   
   
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Web.Services.WebServiceBindingAttribute(Name="WebServiceSoap", Namespace="http://tempuri.org/")]
    public partial class WebService : System.Web.Services.Protocols.SoapHttpClientProtocol {
       
< ....  bunch of code removed for clarity .... >
       
        /// <remarks/>
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloWorld", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        public TestType HelloWorld(TestType tt) {
            object[] results = this.Invoke("HelloWorld", new object[] {
                        tt});
            return ((TestType)(results[0]));
        }
       
        /// <remarks/>
        public void HelloWorldAsync(TestType tt) {
            this.HelloWorldAsync(tt, null);
        }
       
< .... bunch of code removed for clarity .... >
 
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.832")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]
    public partial class TestType {
       
        private int xField;
       
        private string nameField;
       
        /// <remarks/>
        public int x {
            get {
                return this.xField;
            }
            set {
                this.xField = value;
            }
        }
       
        /// <remarks/>
        public string name {
            get {
                return this.nameField;
            }
            set {
                this.nameField = value;
            }
        }
    }
   
<.... bunch of code removed for clarity ....>
       
        /// <remarks/>
        public TestType Result {
            get {
                this.RaiseExceptionIfNecessary();
                return ((TestType)(this.results[0]));
            }
        }
    }
}


Notice that TestType is generated locally here.  Now I just delete TestType from the generated file and add a reference to ClassLibrary1 resulting in:

namespace CSConsole2.localhost {
    using System.Diagnostics;
    using System.Web.Services;
    using System.ComponentModel;
    using System.Web.Services.Protocols;
    using System;
    using System.Xml.Serialization;
    using ClassLibrary1;
   
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.42")]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Web.Services.WebServiceBindingAttribute(Name="WebServiceSoap", Namespace="http://tempuri.org/")]
    public partial class WebService : System.Web.Services.Protocols.SoapHttpClientProtocol {
       
< ....  bunch of code removed for clarity .... >
       
        /// <remarks/>
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloWorld", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        public TestType HelloWorld(TestType tt) {
            object[] results = this.Invoke("HelloWorld", new object[] {
                        tt});
            return ((TestType)(results[0]));
        }
       
        /// <remarks/>
        public void HelloWorldAsync(TestType tt) {
            this.HelloWorldAsync(tt, null);
        }
       
< .... bunch of code removed for clarity .... >
 
<.... no more TestTYpe here! ....>  <--------------------removed the TestType class
   
<.... bunch of code removed for clarity ....>
       
        /// <remarks/>
        public TestType Result {
            get {
                this.RaiseExceptionIfNecessary();
                return ((TestType)(this.results[0]));
            }
        }
    }
}

Now I am using the library version of TestType - web service call still works.
0
 
sirknight1919Author Commented:
drichards:

Yes it's work, I agree. But it's recommended as well. You don't have to modify/edit references.cs ever time there is an update on web services. The client application are located in different location physically. And you don't want to update changes in each location, unless you have a solution that dynamically modifies each clients web reference.
0
 
drichardsCommented:
I am not exactly sure what you are driving at since in one post you say "we want to maintain tightly coupled client and server" but then you are saying you don't want to update clients every time the service changes.  That would say you do NOT want a tight coupling.  What ideally are you after?

I still think generating stubs from the WSDL is the best solution since the WSDl is platform-independent and allows more flexibility.  With it you can update the server stub, the client stub, or the types independently depending on the scenario.  It makes interop between web service implementations a little easier as well.
0
 
sirknight1919Author Commented:
drichards:
I will get back to you. I will study your given code.. but I think your statement in your last post (second paragraph) is an ideal one. My question is, how do i deploy clients if i have changes in my server web services? Such as new web services host location or additional web method.
0
 
sirknight1919Author Commented:
drichards:

Your option #1 is actually what we did the past days.. But the problem is everytime you have changes in your web services you have to update you web service reference and edit again the reference.cs which is we do not want to do.

generating stubs from WSDL as you said is the best solution i guess. You hit the the keywords we want to achieve, "update the server stub, the client stub, or the types independently depending on the scenario". In this, as i understand, we will create a proxy for web service. Am i right?

Btw, this is for our versioning problem of our program.. To give you an idea... our application is deployed in different stores.  Stores need to update other stores for client details and orders. Stores #1 may have different version of software/and database in Store #2. This should not prevent from synchronizing data. The objects will be passed as serialized objects, and the receiving end will deserialized it. No matter what the version of the receiving end, It should handle the data.
0
 
surajgupthaCommented:
The best way for you to make sure that the latest client exists on all machines is by using Click Once Deployment. You can put your latest client on IIS and everytime any users clicks on the link to the application, the COD checks to see if a new version is available, if it is available it would download the latest version of the client to the user machine...

About the code to serialize, deserialize i will put it in a while. Using the serialize/ deserialize option you dont have to touch the WSDL generated code...
0
 
surajgupthaCommented:
       /// <summary>
        /// Copies the object instance of a particular type to the destination type.
        /// </summary>
        /// <param name="source">The Type of the source object instance.</param>
        /// <param name="sourceToCopy">The actual instance to be coverted to destination type.</param>
        /// <param name="destination">The desination Type to which the source object instance needs
        /// to be comverted.</param>
        /// <returns>An object instance of the destination type.</returns>
        public object Copy(Type source, object sourceToCopy, Type destination)
        {
            XmlSerializer serializer = new XmlSerializer(source);
            MemoryStream memoryStream = new MemoryStream();
            XmlSerializerNamespaces nameSpacesList = new XmlSerializerNamespaces();
            nameSpacesList.Add("", "");
            serializer.Serialize(memoryStream, sourceToCopy, nameSpacesList);

            byte[] byteArr = memoryStream.ToArray();
            ASCIIEncoding encoder = new ASCIIEncoding();
            string xmlString = encoder.GetString(byteArr);
            ASCIIEncoding newEncoder = new ASCIIEncoding();
            byte[] newByteArr = newEncoder.GetBytes(xmlString);
            MemoryStream newMemoryStream = new MemoryStream(newByteArr);
            XmlSerializer deSer = new XmlSerializer(destination);
            object deserializedObject = deSer.Deserialize(newMemoryStream);
            return deserializedObject;
        }

Just type cast the object that gets passed back to (ClassLibrary1.Foo)
0
 
drichardsCommented:
I like the Click Once deployment option for managing the client updates.  That way there shouldn't be a case of hitting the service with a different version client.

As for the other part:
>> Using the serialize/ deserialize option you dont have to touch the WSDL generated code...
Why would you touch the WSDL-generated code anyway?  Is that assuming that ClassLibrary1 was updated on the server and the client was using a different version?
0
 
surajgupthaCommented:
>> Why would you touch the WSDL-generated code anyway?  Is that assuming that ClassLibrary1 was updated on the server and the client was using a different version?

There were some posts that dealt with striping away the Type code and using the actual classlibrary instead. So what i meant to say was the developper could still use the classlibrary code instead of the WSDL "Generated" CS code by using the serialization/ Deserialization technique
0
 
sirknight1919Author Commented:
Great minds in here... please give me some hours to play with your suggestion. I will update you immediately on my developments.
0
 
sirknight1919Author Commented:
surajguptha:
When I do your code I have error on XmlSerializer saying that Invalid argument. I would also like to do this in binaryformat later on. Please correct me if my parameter is correct based on the scenario I have given above.

source = MyLibrary.Foo()  // class object
sourceToCopy = (object)MyLibrary.Foo() // type cast to Object Type?
destination = MyLibrary.Foo() // class object with different version, different # of properties.





0
 
sirknight1919Author Commented:
drichards:
Have you already sample application on the solution you are recommending to?
generating stubs from the WSDL?
0
 
sirknight1919Author Commented:
surajguptha:
Is it possible if we can do this in BinaryFormat instead of XmlSerializer?
0
 
sirknight1919Author Commented:
surajguptha:
Error occured when I pass serialized memoryStream to webservices..

 System.InvalidOperationException : There was an error generating the XML document.
  ----> System.InvalidOperationException : The type System.IO.MemoryStream was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

I do the serialization in the client side and just pass the memoryStream type casted to Object. my web services has a web method that receives the Object memoryStream and returned back as MemoryStream type. then do the deserialization in the web services.

0
 
surajgupthaCommented:
We cannot use BinarySerializer to do this since Binary preserves the name spaces too

Here is a sum up just to make sure we are insync
1) Create a proxy to call a wcf service. proxy of wcf service creates a new set of classes which is an exact copy of your class library but in a different names pace

2) Since they are in two different namespaces, you are unable to say ClassLibrary.Foo = proxy.Foo

3) to circumvent that we use the deserilization/ serialization method.
Ex. proxy.foo source = proxy.getinfo();

classLibrary1.Foo a = new classlIbrary1.Foo()
a = (classLibrary1.Foo) Copy(Typeof(source), object source , Typeof(classlibrary1.Foo));

a is the object you want to start using which has the namespace ClassLibrary1.Foo

0
 
sirknight1919Author Commented:
surajguptha:

In here,  serialization/deserialiazation is actually done in one web method which is

public object Copy(Type source, object sourceToCopy, Type destination)
{}

We aim to do the serialization on client1 -> pass the serialized object to web service -> web service pass the serialized object to client2 (thru web service again) -> client2 will deserialized the object -> send acknowledgment back to client1 (status report).
0
 
sirknight1919Author Commented:
Ooopss,, I have to add that there is a web service on the corporate host.. client1 and client2 cannot talk directly.. they must communicate on corporate host web service.
0
 
surajgupthaCommented:
1) Deserialization is automatically done by the framework. You are basically looking to take the proxy object and use them as a classlibrary right??

2) What errors are you facing now?

3) Is the web service hosted on the internet? Can you make the code available? A simpler stripped down version?
0
 
sirknight1919Author Commented:
I really don't want to use the code generated in proxy class...

My error is when I pass object (ClassLibrary)  to my web service. As i search for this, Web services does not support all types of data, especially your custom object except for xml/soap formatted.

My web services is located in 3 sites.. store1, corporate host(accessible thru VPN/Internet), store2.

My temporary solution for this and based on your code is, I serialized my object class on store1 -> passed to corp web services as byte[] data type -> pass to store 2 web services as byte[] -> store2 web services call remote object thru remote host server also located in the site of store2 -> remote objects/class will do the actual deserialization.

I hope I make sense. :)
0
 
surajgupthaCommented:
Ohhh yeah it makes a lot of sense now.. What problem are you facing now? :)
0
 
surajgupthaCommented:
>> Web services does not support all types of data,
Unless you pass something like "object" or arraylist or hasttable. Mostyle everything else should get thro the web services.Just to confirm, what type of data are you trying to pass?. From your code it seems to me that you are just passing a class object isnt it?
0
 
sirknight1919Author Commented:
surajguptha:
Yes.. I'm passing a serialized class object. And even if I type casted this as object type I still cannot pass this to web service since web service.
0
 
sirknight1919Author Commented:
surajguptha:
can you also pls explain line by line of your code above?
0
 
surajgupthaCommented:
Can you post the code you are using to serialize it ? And the code you are using to type cast it including the method signatures?
0
 
sirknight1919Author Commented:
surajguptha:

same as the code above since we cannot use binaryFormat. I just pass the memoryStream to web method but type casted this first to Object type.

serializer.Serialize(memoryStream, obj, nameSpacesList);
Object obj2 = new Object();
obj2 = (Object) memoryStream;
mywebmethod(obj2);


public Object mywebmethod(Object obj)
{
    // deserialization here.
}

Btw, I had already solved this problem by just passing this as byte array instead of object. What I'd like to confirm to you now is: Do I really can't pass serialized custom class/object to a web service? And can you please explain line by line the code you have given above.







0
 
surajgupthaCommented:
The problem with passing an object is that there is no way for the serializer to reflect it. objects like hashtables/ array lists/ objects cannot be passed as arguments.Passing an object was why the error was thrown in your case.

What you did with the byte array is exactly what i was talking about as one of the solutions. I think along the long post we discussion got derailed and ended up serializing/ deseralizing, which was to solve another problem of deep copy to transition bet the proxy classes to class objects
0
 
sirknight1919Author Commented:
surajguptha:
I can stand with this solution for now. I would just like to understand what happens in these line of codes. Can you explain this to me line by line?

        public object Copy(Type source, object sourceToCopy, Type destination)
        {
            XmlSerializer serializer = new XmlSerializer(source);
            MemoryStream memoryStream = new MemoryStream();
            XmlSerializerNamespaces nameSpacesList = new XmlSerializerNamespaces();
            nameSpacesList.Add("", "");
            serializer.Serialize(memoryStream, sourceToCopy, nameSpacesList);

            byte[] byteArr = memoryStream.ToArray();
            ASCIIEncoding encoder = new ASCIIEncoding();
            string xmlString = encoder.GetString(byteArr);
            ASCIIEncoding newEncoder = new ASCIIEncoding();
            byte[] newByteArr = newEncoder.GetBytes(xmlString);
            MemoryStream newMemoryStream = new MemoryStream(newByteArr);
            XmlSerializer deSer = new XmlSerializer(destination);
            object deserializedObject = deSer.Deserialize(newMemoryStream);
            return deserializedObject;
        }
0
 
surajgupthaCommented:
Sure
Type Source -> Class Type of the object that needs its namespace converted
Type Destination -> Class Type of the destination class type desired
SourceToCopy -> Is the actual object that needs to be converted to an object of another class type

block by Block explanation

// Opens a new serializer indicating that the type that is intended to be converted is of type Source
XmlSerializer serializer = new XmlSerializer(source);
// Creating a memory stream for the serializer to convert the object
MemoryStream memoryStream = new MemoryStream();
XmlSerializerNamespaces nameSpacesList = new XmlSerializerNamespaces();
nameSpacesList.Add("", "");
// Does the actual deserialize where the object is converted to xml strings and stored in the memory stream object passed
serializer.Serialize(memoryStream, sourceToCopy, nameSpacesList);
byte[] byteArr = memoryStream.ToArray();
ASCIIEncoding encoder = new ASCIIEncoding();
// Convert the memory stream to a XML String
string xmlString = encoder.GetString(byteArr);
ASCIIEncoding newEncoder = new ASCIIEncoding();
byte[] newByteArr = newEncoder.GetBytes(xmlString);
// Loading the new memory stream with the xml string
MemoryStream newMemoryStream = new MemoryStream(newByteArr);
// making a new serializer of type destination type
XmlSerializer deSer = new XmlSerializer(destination);
//loads the memory stream back to the newly created object
object deserializedObject = deSer.Deserialize(newMemoryStream);
return deserializedObject;
0
 
sirknight1919Author Commented:
surajguptha:
You may still add explanation on XmlSerializerNamespaces  part..

drichards:
I also give you very small portion of points (20) because you open up other possibilities although you did not provide how to do it. I will research on how to do that in the future.
0

Featured Post

Upgrade your Question Security!

Add Premium security features to your question to ensure its privacy or anonymity. Learn more about your ability to control Question Security today.

  • 18
  • 11
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now