Dale Preston's Web Log
  
Sunday, February 06, 2005
 

Returning Custom Classes from a Web Service


Returning custom classes from a web service, the series:

Part 1 — A public data-only class on the web service

Part 2 — A shared class library

Part 3 — Advanced methods

Part 4 — .Net 2.0 Update

One of the questions I often see, and try to respond to, on Microsoft's MSDN managed newsgroups is about returning custom classes from web services. Since it comes up so often, I felt it was worth writing an article, or series of articles, to demonstrate.

XML Web Services offer a specific list of available return types:
  • Boolean
  • Byte
  • Double
  • Datatype
  • Decimal
  • Enumeration
  • Float
  • Int
  • Long
  • Qname
  • Short
  • String
  • TimeInstant
  • UnsignedByte
  • UnsignedInt
  • UnsignedLong
  • UnsignedShort
  • Arrays of all the above

Microsoft, using the SOAP protocol, added support for passing DataSet, XmlNode, classes, and arrays of DataSets, XmlNodes, or classes to Web Services in ASP.Net. There isn't any magic to returning custom classes. It's built in, though perhaps obscurely documented.

What seems to confuse many people about passing and returning custom classes in a web service centers around namespaces.

Consider the scenario where you create a class Person. You copy that class definition to your web service and to your ASP.Net application. Next you try to compile your probject and are surprised by an exception that says WebServiceName.Person cannot be implicitly converted to WebAppName.Person. What happened, you ask? They're both created from copies of the exact class. The problem, as you can see, is in the namespaces. While they may be identical classes, they are not the same class.

What we have to do is make sure that we're comparing apples to apples and not apples to oranges - or in one case, as you'll see later, we'll trick ASP.Net into thinking our orange is an apple.

The somewhat obvious (after you've seen it done and explained, of course) solution is to use just one copy of the class library or of the class code at the web services side. In the following example, I will demonstrate getting a news item (in only the simplest of terms) from a web service and return a NewsItem class as the result of my WebMethod.

First, let's look at some supporting classes. NewsItems must be written by one or more Reporters. Reporters are invariably People. So let's define first the Person class and then inherit from that class to create a Reporter class:

// A base Person class to describe all humans.
public class Person
{
private string name;
private int age;
private string city;


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

public int Age
{
get { return age; }
set { age = value; }
}

public string City
{
get { return city; }
set { city = value; }
}
}


// A Reporter is a Person who works for a news Agency.
public class Reporter : Person
{
private string agency;

public string Agency
{
get { return agency; }
set { agency = value; }
}
}


Notice that these two classes, and the NewsItem class below, are public classes. They need to be public so we can access them from the web page client later.

The NewsItem class includes an ArrayList of Reporters to identify the authors of the NewsItem:

// A NewsItem to return and display
public class NewsItem
{
private int _NewsId;
private string _NewsTitle;
private string _NewsDate;
private string _PicName;
private string _NewsSummary;
private ArrayList reporters = new ArrayList();

public int NewsId
{
get{return _NewsId;}
set{_NewsId=value;}
}

public string NewsTitle
{
get{return _NewsTitle;}
set{_NewsTitle=value;}
}

public string NewsDate
{
get{return _NewsDate;}
set{_NewsDate = value;}
}
public string PicName
{
get{return _PicName;}
set{_PicName = value;}
}
public string NewsSummary
{
get{return _NewsSummary;}
set{_NewsSummary = value;}
}

// This will be returned as an object[] by the XmlSerializer
public ArrayList Reporters
{
get { return reporters; }
set { reporters = value; }
}
}


And to finish up our NewsService web service, a WebMethod that returns a NewsItem on demand. Here's the entire NewsService.asmx.cs file trimmed for the classes shown above:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;

// included to add the XmlInclude attribute
using System.Xml.Serialization;

namespace NewsServiceDemo
{
/// <summary>
/// Demonstrates returning custom classes from a web service
/// </summary>
[WebService(Namespace="http://DalePreston.com/NewsServiceDemo/",
Description="Demonstrates passing custom classes from web services.")]
public class NewsService : System.Web.Services.WebService
{
public NewsService()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}

#region Component Designer generated code

//Required by the Web Services Designer
private IContainer components = null;

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}

#endregion


// Without the XmlInclude attributes, the XmlSerializer would not be
// able to serialize the Reporters ArrayList and it would return null
// for the Reporters property.
[WebMethod]
[XmlInclude(typeof(Reporter)), XmlInclude(typeof(Person))]
public NewsItem GetItem()
{
// Create the NewsItem to return
NewsItem item = new NewsItem();

// Populate the properties with some sample data. This could
// easily be done by getting the values from a database
item.NewsDate = DateTime.Now.ToShortDateString();
item.NewsId = 1;
item.NewsSummary = "No one was killed today.";
item.NewsTitle = "Special Report!";
item.PicName = "NewBornBaby.jpg";

// Create two Reporter objects to store in the Reporters ArrayList
// The first Reporter
Reporter reporter = new Reporter();
reporter.Age = 49;
reporter.Name = "Dale";
reporter.City = "Park Hill";
reporter.Agency = "UPI";
item.Reporters.Add(reporter);

// The second Reporter
reporter = new Reporter();
reporter.Age = 50;
reporter.Name = "Jim";
reporter.City = "Tulsa";
reporter.Agency = "CNN";
item.Reporters.Add(reporter);

// Return the NewsItem class
return item;
}

}
// NewsItem Class here

// Person Class here

// Reporter Class here

}


While this demo service manually populates the properties of the NewsItem and Reporters, in a real world application data to be returned in your custom classes will probably come from some database or other service.

Notice that we add two Reporters to the Reporters ArrayList. The Reporters ArrayList will be converted to an object[] array before returning our NewsItem class.

Another interesting point about the code above is the XmlInclude attribute. By default, the XmlSerializer will try to serialize all serializable public properties and serializable public fields for transfer to the client. The problem is that it doesn't know, by default, how to serialize a Person or a Reporter. The XmlInclude attribute directs the XmlSerializer to look for the type definitions of those two classes before trying to serialize the NewsItem to return from the GetItem() method. You will need to add using System.Xml.Serialization; to your using section or spell out the entire class name for the attribute when you use it.

Try this code, and comment out the XmlInclude attribute. You'll see that the WebMethod still works and still returns a NewsItem. The difference, without the XmlInclude attributes is that the Reporters array will be returned as null and you'll get an exception in the sample WebForm to follow if you don't change it to check for null. Of course, we are using the attributes so we don't have that problem. Keep in mind that in the NewsService web service, and in the WebForm below, I have not included any robust exception handling or security; this is as simple as possible to demonstrate the functionality intended.

That is all there is to developing a web service that will return custom classes including classes with objects as properties.

Now that we've created the web service, all we have to do is consume it. It is as easy as creating a blank WebForm and adding a WebReference to our NewsService web service.

Once that is done, to test the functionality all we need to do is add the following section to the Page_Load event handler:

// Create the connection to the web service proxy
WebServiceReturnTypeDemo.NewsService.NewsService news =
new WebServiceReturnTypeDemo.NewsService.NewsService();
// Set security for the web service - may not be required in all environments
news.Credentials = System.Net.CredentialCache.DefaultCredentials;

// Create the news item
NewsService.NewsItem item = news.GetItem();

// Display something from the NewsItem to prove we got the item returned
Response.Write("News item summary: "
+ item.NewsSummary
+ "<BR><BR>");

// Loop through the Reporters array. The original ArrayList from the
// web service is translated into an object[] by the XML Serializer but we
// did get the values!!!
for(int count = 0; count < item.Reporters.Length; count++)
{
NewsService.Reporter reporter = (NewsService.Reporter)item.Reporters[count];
Response.Write("Reporter " + count.ToString() + " name: "
+ reporter.Name
+ "<BR>");
Response.Write("News agency: " + reporter.Agency + "<BR><BR>");
}


The first significant thing here is the line that says NewsService.NewsItem item = news.GetItem();. Notice that we declared item as an instance of the NewsItem class declared on the NewsService web service. By instantiating the class from the web service, then our classes are now equal. We're now talking apples and apples. Our NewsItem is of the exact same namespace and type as the web service's NewsItem.

The other thing to note is that we use the .Length property to loop through the Reporters. Reporters was an ArrayList on the web service with a .Count property but no .Length property. The framework, and specifically the XmlSerializer, converted our ArrayList Reporters to an array of objects containing Reporter items.


That's all there is to it. We have returned a complex custom class from our web service.

Next time, I'll talk about how to create the NewsItem class locally at the ASP.Net WebForm application rather than having to go to the web service to get the definition.

Download the updated source code for this series.

Comments:
Awesome article. It was so clearly written. Thank you!
 
Very Good Article
 
Thanks a lot. I was looking for this for a long time...
 
You have really help me! Thanks a lot!
 
I'm newbie to Web Service and I came across this problem, this article helped me in passing correctly my custom objects. Thanks and great job!
 
Awesome article, its like spoon feeding for babies :).

Thanks a lot.
 
Thank you very much for the article.
But there's one problem:
by downloading the example
it seems to me that actually you're not calling the same NewsItem object.
You simply called you Web Reference: NewsServiceDemo1WS as the namespace of the WebService.
But actually the NewItems class defined in the WebService is still different from the one we use in the web page.
If you select the NewItem instance in the Page_Load you will get the reference.cs file.
 
You're right, Matt. In reality, there is no way to pass an object from a web service to a client. All of the tricks here are to fake the client so that it appears to be the same object. Just like you can't really pass a System.String because a System.String is actually a representation of a string in memory - and you can't access the server memory from the client. But you don't care because when you write code, the framework handles that in such a way that you don't worry about that slight technicality. Same thing with the tricks in this series of articles. If the object looks like a duck, smells like a duck, quacks like a duck, then, from the perspective of your client program, it is a duck. In fact, all of this is only to simplify writing code. The user at the client doesn't care. All of these tricks are so the developer doesn't have to worry about all this silly namespace stuff.

Hope this clears it up some.

Regards,

Dale
 
I'm in the process of web service design. There's a necessity to return a custom class, so I googled "webservice custom class" and found this article. Solved the problem. Thanks!

John Williams
http://johnwilliams.eu
 
This is very nice article i have used this concept but i am facing one problem of using this code.

i am not getting newitem class inside
the NewService.

so that why getItem function return null

can you help me out what i am missing

ruy.sharma@gmail.com
Thnaks
John
 
John,

Since I don't have your code, I can't tell you what's wrong. I know that the samples have been used by a lot of people so I can only assume you missed something that will probably be obvious once you find it. All I can suggest is to download the samples again, follow the instructions in the readme and see what happens. I won't make any promises because I am very busy with work but if you can't get the sample to work, send me your code in a zip file and, if I get a chance, I'll look at it. Dale-at-dalepreston-dot-com.
 
Hi Dale,

This article is VERY helpful when starting to learn about web services. You covered very well the basics.

I could see a lot of other examples out there... but I couldn't solve this "XmlInclude" mistery (having the wsdl with empty definition for the embedded classes) until now... this article explains it!

Will go and try tomorrow morning and I am sure it will work...

If still not, I will contact you regarding my problem.

Thank you very much!
 
Post a Comment

<< Home

Powered by Blogger