Tuesday 10 January 2012

Web Service in ASP.NET

Building a Web Service in ASP.NET 3.5

The steps to build a web service in ASP.NET 3.5 haven't changed much from previous versions but there are few new things to keep in mind. The biggest new feature in ASP.NET 3.5 are the "AJAX-enabled" web services. You can now set up your web service to return in either the standard SOAP/XML format or "AJAX-enable" your service with a single setting that will allow you to call your Web Service methods directly in javascript in your ASPX pages.
Building a Simple Web Service

To create a web service, first add a new .ASMX file to your web project by right-clicking your web project and selecting Add New Item.

Choose Web Service from the horribly unsorted list (Why can't we get this alphabetized!!!). Then choose a name for your service. I recommend selecting "Place code in separate file". This setting will create a "ExampleWebService.asmx" file at the project root or where ever you are currently adding this service and will put the actualy C# code for the services in your App_Code directory as a file called "ExampleWebService.asmx.cs". Then click Add.
visual studio add new item - web service
Your new service should look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
 
/// <summary>
/// Summary description for ExampleWebService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 

// [System.Web.Script.Services.ScriptService]
public class ExampleWebService : System.Web.Services.WebService
{
public ExampleWebService()
{
//Uncomment the following line if using designed components 
//InitializeComponent(); 
}
 
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
Setting up the Web Service code

The first thing you need to do is change your URI namespace. By default, it is set to http://tempuri.org/. But you want this to a completely unique URI string to you and/or your organization. It does not have to be a valid URL to an actual page (although that's a good idea if you want to publish a user guide or usage policy there). I normally just use the URL to where this web service will reside in production. For instance, I might put: http://www.devtoolshed.com/webservices/examplewebservice/. NOTE: It's always good to add the final "/" to end of your URL if it is to a directory path to make it a well-formed URL.
The next thing to notice here is the section just below that says "To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line." If you uncomment this line, it will "AJAX-enable" your service so that you can call it using the ASP.NET AJAX framework and javascript. Very useful. But if you aren't using this feature, just leave it commented out.
It's kind of lame but Microsoft puts a sample "HelloWorld" web method already in there so you can see the format (I guess it's tradition because this has been in there since ASP.NET 1.0). For a simple service that just returns a base data type such as int or string, this HelloWorld method is a good example.
Notice in the HelloWorld method, the way that you enable this method to be called remotely is by using the "[WebMethod]" attribute above the method signature. When WebMethod is placed above a public function in this web service class, ASP.NET does ALL the work for you. This really is the best part. It will automatically add your method to the WSDL definition of the service. It will wrap your return values as well as parameters in SOAP for you. You literally just code this like a normal method and all the SOAP and security layer work is done automatically for you.
NOTE: You can add as many [WebMethod]'s as you like to your web service class and they will all be available as remotely executable functions for your clients invokers.
[WebMethod] Description parameter

There is an extra parameter that is available for the [WebMethod] attribute that I like to add called Description. This allows you to describe the web method. Just for fun, I've added a parameter to the HelloWorld service as well to pass a message to the string.
Here's an example using the Description parameter for the HelloWorld method:
[WebMethod(Description = "Says hello to the world!")]
public string HelloWorld(string sMessage)
{
return "Hello World" + sMessage;
}
Running the Web Service

Now you can run this web service by right-clicking the ExampleWebService.asmx file and choosing "Set as start page" and hitting F5. ASP.NET automatically generates a run-time wrapper for your services so you can test, step into code, and make sure you have them all coded correctly before going live. You should see a page something like this:

web service list of operations
If you click on the HelloWorld method link, you can actually run the service. ASP.NET will even create a simple web form that you can enter the parameter values for your method to invoke it. Type a message and then click "Invoke".

invoke a web service method
You should have an XML return value that looks something like this:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://www.devtoolshed.com/webservices/examplewebservice/">Hello World you look wonderful!</string>
NOTE: The SOAP wrapper is not shown here to keep things simple when developing but it will be added automatically when this service is invoked via a SOAP enabled client.
Web Services that return Complex Types

For simple web services (not very common), this works great. But what if you want to actually pass a complex type such as an object your created as the return value or even accept one as a parameter to your method? Luckily, this is also supported out of the box.
Here are a few things to keep in mind when doing this:

  • If you pass a complex type as a parameter, then ASP.NET will not be able to generate the run-time web page wrapper we just used. It won't know how to translate your custom object to a base data type. So to test your methods, you will need to use a simple client application to call your web services so you can step into code, etc.
  • I HIGHLY RECOMMEND that when you return complex types as return values, you do NOT use your direct data access layer objects. There are a couple of reasons for this. 1) Web services, once in production and being used, become one of your most brittle interfaces. If you decide to change your database object down the road, then as soon as you do, it will ripple effect through your services and break every interface. 2) Once your service is being called by client applications, they would all need to be updated each time you made a change to your data access layer objects.
  • NEVER return Strongly Typed DataSets as return values! There is a known limitation in ASP.NET which limits you to 1 method per web service class that can return a DataSet. This has to do with the fact that DataSets are actually stored as XML and the service itself is XML so there are conflicts. I ran into this issue a while back and it took a long time to figure out. The only work around we could ever find was to create a separate web service class for each method that returned a strongly typed DataSet. We had 10's of web service objects and it was a nightmare to manage.
What I've found that works great is to isolate your interfaces for your web services into a namespace. I usually call this namespace something like "WebServiceTypes". I recommend putting every object type that is returned as a value and is passed as a parameter to your web services here. Here's an example of how to do this:
Complex Type Example

First, create a new directory in your App_Code directory called "WebServiceTypes". Let's change HelloWorld to return a complex type.
Add new Class to the WebServiceTypes directory called "ExampleComplexType". Here's some code to make it a complex type:
namespace WebServiceTypes
{
/// <summary>
/// An example of a complex type to be used as a return value in a web service.
/// </summary>
public class ExampleComplexType
{
public ExampleComplexType()
{
// constructor
}
 
// unique id of the message
public int MessageId { get; set; }
// message to say hello
public string HelloMessage { get; set; }
// message to say world
public string WorldMessage { get; set; }
}
}
Modified HelloWorld Method

Here is the HelloWorld method changed to return ExampleComplexType as the return value:
[WebMethod(Description = "Says hello to the world!")]
public WebServiceTypes.ExampleComplexType HelloWorld(string sMessage)
{
// create new complex type object to be returned
WebServiceTypes.ExampleComplexType complexType = new WebServiceTypes.ExampleComplexType

();
 
// fill object with values to return.
// NOTE: here you could do a database query or file system read
// to get the values to build up you complex type properties.
complexType.MessageId = 1234;
complexType.HelloMessage = "Hello";
complexType.WorldMessage = "World " + sMessage;
// return the complex type
return complexType;
}
Now run the web service again and the XML returned should look like this:
<?xml version="1.0" encoding="utf-8"?>
<ExampleComplexType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.devtoolshed.com/webservices/examplewebservice/">
<MessageId>1234</MessageId>
<HelloMessage>Hello</HelloMessage>
<WorldMessage>World is so wonderful!</WorldMessage>
</ExampleComplexType>
The nice thing about doing a WebServiceTypes namespace is that you are translating your objects for the web service calls so even if your data access classes or any other objects change, your web services will still work and you won't break your client callers.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete