From an open-standards perspective, Web Services are simply a way to provide interoperability between computing systems. In .NET, Web Services are built with ASP.NET technology, giving us more capabilities than what the open standards specify. This is a benefit because sometimes people like to take advantage of platform-specific capabilities. The tips and tricks in this article are a collection of tasks that people often want to do with ASP.NET Web Services. We'll look at the implementation, tradeoffs, and reasons why you would want to use each item, hopefully giving you new tools that will help in your software development projects.
Exception Handling
When an exception is generated, you normally expect to be able to handle it with a catch block matching the exception type thrown. However, this doesn't happen with Web Services. When an exception is generated from a Web Service, it is returned in the caller's code as a SoapException.
While you may receive the message and stack trace from the original exception, you could lose important information from custom exceptions. To remedy this problem, catch the original exception in the Web Service method and wrap it in a new SoapException instance, set the Detail property of the new SoapException to the custom information you need to retain, and throw the SoapException. The following code demonstrates this:
[WebMethod]
public string GenerateException()
{
XmlDocument doc = new XmlDocument();
XmlNode node = doc.CreateNode(
XmlNodeType.Element,
SoapException.DetailElementName.Name,
SoapException.DetailElementName.Namespace);
node.InnerText = Environment.MachineName;
SoapException se = new SoapException(
"This is a demo SoapException",
SoapException.ClientFaultCode,
Context.Request.Url.AbsoluteUri,
node);
throw se;
return "";
}
The GenerateException method above returns the name of the current machine this Web Service is running on as the Detail property of the SoapException. The Detail section is set with an XmlNode object named node. Notice how node is instantiated by passing the Name and Namespace properties of SoapException.DetailElementName as part of the doc.CreateNode method call. Using these properties is essential for ensuring the Detail element is properly constructed.
Tracing
ASP.NET Web Services can take advantage of ASP.NET Tracing facilities. This is handy if you need to do some debugging and want a quick and easy way to get the output. As with an ASP.NET Web Application, you can enable tracing by setting the trace element in Web.config to true, as follows:
enabled="true"
requestLimit="10"
pageOutput="false"
traceMode="SortByTime"
localOnly="true"
/>
In the code, you must explicitly pull the Context from HttpContext as shown in the following example:
[WebMethod]
public string MakeTraces()
{
HttpContext.Current.Trace.Write(
"Trace.Write() from MakeTraces() Web Method.");
HttpContext.Current.Trace.Warn(
"Trace.Warn() from MakeTraces() Web Method.");
return "Open Trace.axd to view output.";
}
The example above accesses the Warn and Write methods of the Context object. You can open the Trace.axd file, under the virtual directory to view trace output.
Web Services are stateless by design
Session Management
Web Services are stateless by design, which is necessary for scalability. However, there is still a way to retain Session state with ASP.NET Web Services. By default, Session state is turned off in Web Services, but you have the option to turn it on. The following example shows how to turn on Session state for a Web Service:
[WebMethod(true)]
public string GuessANumber(int guess)
{
if (Session["number"] == null)
{
Random rand = new Random();
Session["number"] = rand.Next(10) + 1;
}
string result = null;
int number = (int)Session["number"];
if (guess == number)
{
result = "You are correct!";
}
else if (guess < number)
{
result = "Higher.";
}
else
{
result = "Lower.";
}
return result;
}
The WebMethod attribute has a constructor with a Boolean positional parameter that turns Session state on when the value is true. The GuessANumber method above uses the Session state, just as you would in an ASP.NET Web Form.
When using the Web Service helper screen, Session state is managed by the browser. However, if you wanted to call a Web Service from a Windows Forms client, you would have to add extra code to manage the session. The following example demonstrates code that manages session cookies in a Windows Form application:
public Form1()
{
InitializeComponent();
svc = new SessionClient.localhost.Service1();
svc.CookieContainer = new System.Net.CookieContainer();
}
// type members
localhost.Service1 svc = null;
private void btnGuess_Click(object sender, System.EventArgs e)
{
lblResult.Text = svc.GuessANumber(Int32.Parse(txtGuess.Text));
}
Notice the Form1 constructor above where it initializes the CookieContainer property of the Web Service reference. Normally, the CookieContainer is null, but we need it to hold the session cookie that will be sent to the Web Service to manage Session State.
The btnGuess_Click method is a callback from a button on the Windows Forms client. When making the call to GuessANumber, the Web Service populates the CookieContainer with the cookie used for session management. Since we are using the same Web Service reference, the same CookieContainer is used on each call.
Caching
With Web Services, many people are interested in performance. To assist with this, ASP.NET Web Services support caching. For any given Web Service method, you can set its cache so it will hold results in memory for a specified amount of time. The following example shows how to set up Web Service caching:
[WebMethod(CacheDuration=10)]
public string GetTime()
{
return DateTime.Now.ToString();
}
The WebMethod attribute of the GetTime method above contains a named parameter, CacheDuration. Every 10 seconds, the cache for this method will be invalidated and the next Web Service method call will update the cache with its results.
Asynchronous Invocation
Another performance issue occurs naturally because of overhead due to network calls to the Web Service. To help maximize application performance on long running processes, .NET supports asynchronous programming. In particular, ASP.NET Web Services tools work via IDE reference or command-line to generate managed proxies that include asynchronous Web Service calls. The following code shows how to perform asynchronous calls on Web Services:
// Web Method in ASP.NET Web Service
[WebMethod]
public string CallMeAsync()
{
return "You are Async.";
}
// asynchonous call to Web Service from client
private void btnCallMeAsync_Click(object sender, System.EventArgs e)
{
localhost.Service1 svc = new AsyncClient.localhost.Service1();
IAsyncResult ar = svc.BeginCallMeAsync(null, null);
ar.AsyncWaitHandle.WaitOne();
lblResult.Text = svc.EndCallMeAsync(ar);
}
There are two parts to the code above: a Web Service method and a client method. The Web Service method, CallMeAsync, is a normal method, defined like any other Web Service method. The client method, btnCallMeAsync_Click, makes an asynchronous call to the Web Service method. The BeginCallMeAsync and EndCallMeAsync were automatically generated and added to the proxy in the Web Reference that was created for the client. Similar methods of the form BeginX and EndX are automatically generated for any Web Service method X.
Generating Proxies
The VS.NET IDE automatically generates a Web Service proxy when you right-click on References and select Add Web Reference. This is very convenient, adequate, and the preferred method of generating a proxy whenever you can do so.
VS.NET IDE automatically generates a Web Service proxy
Another method of generating a Web Service proxy is to use WSDL.exe. WSDL.exe is a command-line utility that comes with the .NET Framework SDK. Here's an example of how it works:
C:\Documents and Settings\Joe>wsdl.exe http://localhost/AsyncService/Service1.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Writing file 'C:\Documents and Settings\Joe\Service1.cs'.
This example was simple enough, but didn't illustrate any advantage over creating a Web Reference in VS.NET. There are a couple good reasons for using WSDL.exe: more control over proxy creation and protection of the code.
When creating a Web Reference through VS.NET you get the default behavior and can't change how the proxy is generated. However, WSDL.exe gives you several options for customizing the generated proxy. For example, you can set the namespace, server authentication credentials, proxy authentication credentials, and supported protocols.
Another reason for using WSDL.exe is to keep people from accidentally overwriting your proxy. In VS.NET, anyone can right-click on the Web Reference and select Update Web Reference. This is normally okay. However, what if you manually changed the code in the proxy, such as when using Web Service Enhancements (WSE). Updating the Web Reference would wipe out your code and you better hope that your source control can save you. The alternative is to use WSDL.exe to generate the proxy and include it as a file in your project. That way you could change the file all you want without worrying about it being updated by accident.
Dynamic Addressing
When Web Service proxies are generated, they include code that sets the URL of the Web Service to communicate with. This is convenient because it means that the developer has less work. The following code shows the generated proxy code:
public Service1() {
this.Url = "http://localhost/UrlService/Service1.asmx";
}
The code above is the constructor from the Web Service proxy. It sets its Url property to the address of the Web Service.
While this is convenient, it is not without problems. The prime scenario that comes to mind is when the Web Service is deployed from development to a staging or production server. One way to fix this is to set the Url property of the Web Service as shown in the following code:
private void btnGreeting_Click(object sender, System.EventArgs e)
{
localhost.Service1 svc = new UrlService.localhost.Service1();
svc.Url = "http://localhost/UrlService/Service1.asmx";
lblGreeting.Text = svc.HelloWorld();
}
This keeps you from having to generate a new proxy or modifying the existing proxy for the Web Service. In fact, it may be useful in situations where you don't know which Web Service you will use until runtime. The problem is that if you are using a single URL, you still have to recompile your code when the Web Service is deployed to another server.
A better option is to use a configuration file that lets you change the URL without having to recompile code. The Web.config file has an appSettings section that allows you to do just that, as shown below:
<appSettings>
value="http://localhost/UrlService/Service1.asmx" />
The appSettings element holds a set of key/value pairs that allow adding dynamic content via configuration files to your applications. This example used the Web.config file, making the assumption that the client was an ASP.NET application. You can accomplish the same thing by using the Appname.exe.config file for a Windows Forms client. The following code shows how to use information from appSettings:
private void btnGreeting_Click(object sender, System.EventArgs e)
{
localhost.Service1 svc = new UrlService.localhost.Service1();
svc.Url = ConfigurationSettings.AppSettings["HelloServiceUrl"];
lblGreeting.Text = svc.HelloWorld();
}
The ConfigurationSettings class is a member of the System.Configuration namespace. This corrects problems with being able to change the Web Service URL during runtime. One last annoyance is that it does leave you with responsibility for setting the Web Service URL. This is not necessarily a problem, but VS.NET does give you an easier way to handle this.
Locate the Web Reference for your Web Service, right-click on it, and select Properties. One of the properties on the property grid is URL Behavior, which is set to Static by default. Change the value to Dynamic. This creates a new appSettings element and adds a new entry to the Web Service proxy. Here's the new appSettings element:
<appSettings>
value="http://localhost/UrlService/Service1.asmx"/>
The auto-generated appSettings entry uses the fully qualified name of the Web Service as the key. The following code shows the modifications to the Web Service proxy:
public Service1() {
string urlSetting = System.Configuration.ConfigurationSettings.AppSettings
["UrlService.localhost.Service1"];
if ((urlSetting != null))
{
this.Url = string.Concat(urlSetting, "");
}
else
{
this.Url = "http://localhost/UrlService/Service1.asmx";
}
}
Notice that ConfigurationSettings.AppSettings is now part of the proxy. It uses the configuration file setting if available. Otherwise, it defaults to the URL of the Web Service that the proxy was originally generated from. This means that you can call the Web Service from the client without doing anything with the URL.
Summary
This article identified several tips and tricks you can perform with ASP.NET Web Services. Web Services throw SoapException types rather than the exception that was originally thrown. You can add extra information to the exception by using the Detail property of the SoapException. A couple of the ASP.NET capabilities that you can use with Web Services are Tracing and Session state management. For performance improvements, you can implement caching and use asynchronous calls on Web Service methods. When you need additional flexibility in proxy generation, consider the WSDL.exe command-line utility. Finally, you can set the URL Behavior of a Web Service proxy to Dynamic, enabling runtime modification when a Web Service is redeployed to another server.