Keeping DataSets To Yourself When Returning Data in your Web Service

by Marvin 5. March 2009 14:19

When I first starting developing web services, I just loved to use DataSets to return data from the service.  I mean, they are pretty easy to use, and I am all about the easy road.  Then I stumbled across Scott Hanselman's blog article where I learned that "returning DataSets from web services is the spawn of Satan and represents all that is truly evil in the world."  Oops.  While I found this and other articles which explained the problems with using DataSets as a container for returning data from a web service, I didn't really find that "for Dummies" walkthrough that I was looking for.  At least, not a walkthrough that met my particular needs and that my feeble brain could understand.  This article is intended to be that walkthrough I wish I could have found.

DOWNLOAD COMPLETE CODE FOR THIS ARTICLE HERE: WebServiceArticle.zip (119.38 kb)

First, a disclaimer.  I definitely don't know all there is to know about web services.  There is probably a better way than what I am going to show you.  Still, I have learned some valuable lessons about web services in the last several months, and I am certain that what I am going to show you is much better than what I was doing, ie. using DataSets.  If you stumble across this article and see an important point that I have missed or gotten wrong, please post it as a comment. 

I won't go into a length explanation of the problem with returning DataSets from a web service.  I think that was done VERY well in Aaron Skonnard's article on MSDN here: http://msdn.microsoft.com/en-us/magazine/cc188755.aspx.  There is also a pretty thorough explanation with code examples in Wangming Ye's article on IBM's website here: http://www.ibm.com/developerworks/xml/library/ws-tip-j2eenet2.html   

Frankly, you may not see much difference in the content of my article and Wangming Ye's.  His article is definitely more technically thorough.  My objective here is to be more Dummy-friendly for newbie programmers like myself.  In case you aren't going to read those two articles, here's my nutshell summary.  When you return DataSets, DataTables, ArrayLists or pretty much any of the other generically-typed collection objects in the .NET framework from your web service, the section in your .NET-generated WSDL file that describes the structure of your data gets pretty ambiguous.  Those Java, Perl, PHP, etc. programmers out there who try to bind to you service using your WSDL are going to curse you and Microsoft because their toolkits aren't going to be able to accurately decipher the data structure.  This means they'll have to drop down to the XML and write extra code to parse out the data.  Yuck.  

Ok, let's build a .NET web service that even a Perl-programming, Bill-Gates-hater will not complain about.  I'm going to keep it fairly simple for the sake of clarity.  Let's say I have a MySql database called "db_test".  It's got one table called "tbl_recipes" (see the table diagram). 

Field Name  Type  Description 
recipe_id  int  primary key 
recipe_name  varchar(25)  name of the recipe 
ingredients  varchar(500)  comma-separated list of recipe ingredients 
contributor  varchar(100)  name of the person who contributed the recipe 

Our service is going to have one web method called "getRecipesByIngredient".  As the name suggests, it will take a string ingredient as an input parameter and return back a collection of all the recipes that have that ingredient in it.  The collection will include the recipe ids, names, ingredients, and contributor names.  

In Visual Studio 2008, choose File > New Web Site, and choose "ASP.NET Web Service" under the Visual Studio installed templates section (sorry, I haven't learned about WCF services yet).  I won't go into setting up the MySql database here.  If you aren't sure how to do that, check out the "Setting Up Connections to Your MySQL Database" section of this article. 

Right out of the box, Visual Studio gives you a basic "Hello World!" web service already started with the file "App_Code/Service.cs" already open.  For me, that file looked like this: 

Just for good measure, let's customize some things.  At line 8, I'm assuming our service is hosted on a fictious website called "recipes.org" and I'm changing the code to look like this:

[WebService(Namespace = "http://recipes.org/", Description = "The Recipes.org web service is a great resource for your application to use to lookup recipes by ingredient.", Name = "Recipes Web Service")]

When we get done building this service, .NET is going to create this nifty .asmx page for us that describes our service.  The Description and Name properties that I set above will determine what is displayed on that page.  For now, I am going to leave my HelloWorld() method alone and use it as a guideline to create my web method right underneath it.  Before I can do that, though, I need to define a custom type (a class) called "recipe". 

Let me just stop right there and admit that not long ago I felt pretty uncomfortable with my understanding of this notion of creating classes.  Creating them just felt weird.  I wasn't sure I was doing it right at all or even what the purpose really was.  If that's you, let me implore you to check out this article I wrote on using objects.  For me, it's the best stuff I have learned yet. 

Back to business.  To create the recipe class, I right-click on the App_Code folder in my Solution Explorer, choose Add New Item, and pick "Class" from the Templates window.  In the name field, I change "Class1.cs" to "Recipe.cs".  Here's my code for the class.  Pretty basic.

     public class Recipe
     {
        
public int recipe_id { get; set; }
         
public string recipe_name { get; set; }
         public string ingredients { get; set; }
        
public string contributor { get; set; }
     }

Now, I can go back to my App_Code/Service.cs file and create my new getRecipesByIngredient web method.  I am going to be using some ADO.NET objects as well as objects from the .NET Connector to get my data from the database, so I need to add a couple of using statements to the top of my Service.cs file as follows:

     using System.Data;
     using MySql.Data.MySqlClient;

Next, we write the code for the getRecipesByIngredient web method.  Pay particular attention to the comments throughout the code which explain what's going on. 

A much simpler approach to the sql code at lines 34 - 44 would have been to just concatenate the passed in string ingredient to the string SqlCode like this: 

string SqlCode = "SELECT `recipe_id`, `recipe_name`, `ingredients`, `contributor` ";
SqlCode += "FROM `tbl_recipes` WHERE `ingredients` LIKE concat('%" + ingredient + "%');";

This is considered bad practice, though, from a security standpoint.  More information about that here

Another good thing to do which I honestly neglected was adding a description to the WebMethod attribute just above the declaration of the method.  As with the description of the service that we added earlier in the article, this method description will be displayed on the .asmx page that the .NET engine generates for us.  The code looks something like this:
[WebMethod(Description = "Takes an ingredient as a string and returns an array of matching recipes and their ids from the recipes.org database.")]

In regards to understanding the DataSet problem, if you are a visual person like me, you might find it interesting to stop at line 53 and just return the DataSet ds first before adding the code for the rest of the method.  If you do this, you'll need to temporarily change the declaration of the web method from:

[WebMethod]
public Recipe[] getRecipesByIngedient(string ingredient)

to:

[WebMethod]
public DataSet getRecipesByIngedient(string ingredient)

Now, when you are setup to return a DataSet, take a look at the getRecipesByIngredient method documentation on the .asmx page created for you by .NET (this will be at path ../Service.asmx?op=getRecipesByIngedient if you've been following my steps exactly).  Specifically, look at what is displayed for a return SOAP message:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getRecipesByIngedientResponse xmlns="http://recipes.org/">
      <getRecipesByIngedientResult>
        <xsd:schema>schema</xsd:schema>xml</getRecipesByIngedientResult>
    </getRecipesByIngedientResponse>
  </soap:Body>
</soap:Envelope>

Can you tell anything about the data structure of the return message from this?  I can't, and neither can the toolkits that non-.NET developers use to bind to a web service.  However, with the approach outlined in this article, the example SOAP on the same documentation page is much more informative:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getRecipesByIngedientResponse xmlns="http://recipes.org/">
      <getRecipesByIngedientResult>
        <Recipe>
          <recipe_id>int</recipe_id>
          <recipe_name>string</recipe_name>
          <ingredients>string</ingredients>
          <contributor>string</contributor>
        </Recipe>
        <Recipe>
          <recipe_id>int</recipe_id>
          <recipe_name>string</recipe_name>
          <ingredients>string</ingredients>
          <contributor>string</contributor>
        </Recipe>
      </getRecipesByIngedientResult>
    </getRecipesByIngedientResponse>
  </soap:Body>
</soap:Envelope>

See the difference?  That's it.  Hope it helps someone.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading



About the author

I am a junior-level C# .NET developer living in Nashville, TN.  I'm currently working in biomedical informatics, developing a web service, a MySql database, and a web application.  Every week or so, I spend hours trying to figure out how to do something, and after I find the solution, I really want to make sure I don't go through that exercise again.  I love to write.  It helps me to remember things.  So, I use this blog as a way to document those painful lessons as I learn them.  It has already helped me to be able to refer back to them.  I hope some of these will save someone else some time as well.