Using objects with Web Services

In our latest project we encountered a problem with the usage of a Web Service.

The web method we created received an object as a parameter that contained information about the operation we wanted to perform. When I created the web reference to the service a strange thing has happened: Visual Studio created a proxy class for the web service and another proxy class for the type of the object that the web method received. It even became more complicated when the object contained an enum: VS created a proxy for the enum too.

Why this happens?

Web services are supposed to be used with out the user deploying assemblies on his computer. That means that the user needs all of the object that can be used with the web service without recreating them manually or deploying an assembly. When a web method receives an object as a parameter or has an object has a return value, VS creates a proxy class which contains all the properties and fields, so the user can to create this object (which is generated under the web reference namespace) and use it in the call for the web method.

Complicated? not so much. Here's a simple example:

Consider this class:

namespace YsA.Testers.Entities  
{
    public enum MyEnum
    {
        FirstVal = 100,
        SecondVal = 102,
        ThirdVal = 103
    }

    public class MyObject
    {
        private MyEnum enumField;
        public MyEnum EnumField
        {
            get { return enumField; }
            set { enumField = value; }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public MyObject()
        {
            enumField = MyEnum.FirstVal;
            name = String.Empty;
        }

        public MyObject(MyEnum e,string name)
        {
            enumField = e;
            this.name = name;
        }

        public override string ToString()
        {
            return "Name : " + Name + "," + " Enum : " + EnumField.ToString();
        }

        public void DoSomething()
        {
            Name = "YsA";
        }
    }
}

And the web service who uses it:

using System;  
using System.Data;  
using System.Web;  
using System.Collections;  
using System.Web.Services;  
using System.Web.Services.Protocols;  
using System.ComponentModel;  
using YsA.Testers.Entities;

namespace WebServicesTester  
{
    /// <summary>
    /// Summary description for Service1
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    public class TestObject : System.Web.Services.WebService
    {
        [WebMethod]
        public MyObject GetObject(MyEnum e,string name)
        {
            return new MyObject(e, name);
        }

        [WebMethod]
        public MyEnum GetEnum(MyObject o)
        {
            return o.EnumField;
        }
    }
}

I created a web reference using Visual Studio (Right click on the project -> Add web reference).

Finally I created a class which uses the service:

using System;  
using System.Collections.Generic;  
using System.Text;  
using YsA.Testers.WebServiceTesters.localhost;

namespace YsA.Testers.WebServiceTesters  
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                TestObject t = new TestObject();
                MyObject o = t.GetObject(MyEnum.SecondVal, "Test");
                Console.WriteLine(o.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Notice that I'm using MyObject which is under the namespace YsA.Testers.WebServiceTesters.localhost, which is the namespace that was created by Visual Studio and contains all the proxy class for using the web service.

Also notice that the methods in MyObject were not generated, so when we will run this code we will receive
YsA.Testers.WebServiceTesters.localhost.MyObject and not what the overridden ToString() method actually does in the original MyObject.

Another thing: If we print the value of MyEnum.SecondVal what we will get?
The result will be 1 and not 102 as defined in the original enum. This is because proxy enums are generated without there original values:

namespace YsA.Testers.WebServiceTesters.localhost  
{
    public enum MyEnum
    {
        FirstVal,
        SecondVal,
        ThirdVal
    }
}

Now for the problem: What if we do want to use the original objects and not the proxy objects that are generated by VS? Is it possible?

We would like to do this when there is an assembly that is used together with the web service.
For example: We have a solution with a web site and a windows application, and we want to invoke things in the web site (like clearing the cache) when certain actions are preformed in the application. The simplest way to do this is be using a web service that is defined in the web site and can perform actions on the web site (because it is under the web site it can access the application and cache of the web site).
In this case we might have a class which is used in the application and web site and we want to pass an instance in the web method.

To do this we need to use a little hack...

When we create a web reference a folder is created under the project folder named Web References\[Web reference name - entered by the user when creating the reference].
Under this folder Visual Studio generates a couple of files. One of them is called Reference.cs. This file contains the C# code for the definitions of the proxy classes. Here's the example for the MyObject & MyEnum proxy classes:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.312")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]
public enum MyEnum {  
    /// <remarks/>
    FirstVal,
    /// <remarks/>
    SecondVal,
    /// <remarks/>
    ThirdVal,
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.312")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]
public partial class MyObject {  
    private MyEnum enumFieldField;
    private string nameField;
    /// <remarks/>
    public MyEnum EnumField {
        get {
            return this.enumFieldField;
        }
        set {
            this.enumFieldField = value;
        }
    }

    /// <remarks/>
    public string Name {
        get {
            return this.nameField;
        }
        set {
            this.nameField = value;
        }
    }
}

As you can see the enum is defined without values and the object is defined without methods, only fields and properties.

So what you need to do in order to use the original objects?

  • Erase the definition of the object's proxy classes from the reference.cs file (In this case: MyObject & MyEnum).
  • Add a reference for the assembly which contains the original objects to the project which will use the web service.
  • Add a Using statement in the reference.cs class for the namespace which contains the original objects.
  • Save the reference.cs file.

Now if we run the program:

using System;  
using System.Collections.Generic;  
using System.Text;

using YsA.Testers.WebServiceTesters.localhost;  
using YsA.Testers.Entities;

namespace YsA.Testers.WebServiceTesters  
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                TestObject t = new TestObject();

                MyObject o = t.GetObject(MyEnum.SecondVal, "Test");

                Console.WriteLine(o.ToString());
                Console.WriteLine((int)MyEnum.SecondVal);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

The Results will be:

Name : Test, Enum : SecondVal  
102  

Pretty nice.
After you do this remember: When you refresh the web reference the proxy class will be regenerated (which will cause a few compilation errors). Refreshing simply means that requerying the server and recreating the reference, so every time you refresh you will need to delete them again.

I will be glad to here what you think about this solution , and even more if you can find another way to to this (believe me, I looked for some time).

Update

Solution to this issue was suggested by one of the readers.
It enables you to define which classes you want to create when generating a web reference:
http://msdn.microsoft.com/en-us/library/system.xml.serialization.advanced.schemaimporterextension.aspx
http://msdn.microsoft.com/en-us/library/w46ccb0h.aspx

Yossi Shmueli

Technologist, enjoy solving technical challenges for real world applications. Keeping it Green since 1995

comments powered by Disqus