(How)
C^# You Are - 4 November 2007
In honor of a friend of mine's new job writing web services I'm going to target
web services this week.
- What is the difference between a web service and a Windows service? Answer
A web service is effectively a mechanism for communicating between systems (often
on different networks) through a network or the Internet. A web service is
often a simple set of routines that can be called in order to interact with the
remote machine. A web service generally exposes functionality grouped together.
For example you might interact with Amazon.com through a web service to search for
a particular book. If found a separate web service might be used to purchase
the book. Web services help to eliminate the system-specific details in order
to facilitate communication.
A Windows service, on the other hand, is a process that runs on a Windows machine.
This process is special in that it runs in the context of a specific user rather
than in the context of the user who is currently logged on or who started the process.
Services often start execution before any user logs into the system. A Windows
service is no more able to communicate with or run on non-Windows machines as any
other Windows process.
Confusion often enters the picture when someone asks for help working with a windows
service or simply a service. Without more information we can not be sure whether
the person means a Windows service or a web service.
- What mechanism do web services use to communicate? Answer
Web services, in most cases, use SOAP over HTTP. SOAP is a formalized XML
format (text) that makes it easy to send and receive web service requests through
a standard web server. This helps eliminate security issues in the network
firewall and makes it easier to add web services to the system. SOAP is ideal
because parsers for SOAP already exist and SOAP is extensible enough to allow just
about any web service interaction to occur. Newer standards are continually
being released to add new features to SOAP such as encryption, enhanced security
and binding policies.
Web services can also be encoded in other formats (such as binary) but interoperability
starts to suffer so it is often not a good choice.
- How can you send a custom business object through a web service? Answer
This is actually relatively straightforward up front. The custom business
object must be broken down into a simple type that can be serialized to XML and
sent across the wire. The receiver on the other end can build back up the
object from XML and execute the web method.
Where things start to break down is when logic is attached to the business object.
Without writing a custom client for your web service you can not be sure that any
business rules were followed. For example you might be want to pass a coordinate
(lat/long) through a web service. Your business layer might assume that a
lat is between 0 and 360. You should not make this assumption in the web service,
even if you are using a custom client. It is very easy to build up a SOAP
packet and send it to the web service. The service should always validate
all data that is received.
In general you should strive to keep custom business objects out of the web service.
Instead use simple types or collections of types (a structure) to gather up multiple
fields. Enforce the business rules in the web service under all circumstances.
- You wrote a class that contains a method called Foo that you want to expose as a
web service. How do you do it? Answer
Firstly you must identify the class as a web service. This is technically
not required as any method that is marked as a web method will automatically make
the class a web service but it is still good practice. Use the WebService
attribute to identify the web service (which is a type, not a method). This
attribute accepts several optional properties such as a friendly description of
the service and the URL to the service. Oh, the class must be public and be
running inside an ASP.NET application to be a web service.
You will also want
to derive from the WebService class to get the default implementation
for the service. This is technically not required either
but you will not have access to the ASP.NET state management infrastructure without
it.
For each method you want to expose through the service you must mark the method
with the WebMethod attribute. Not all methods need to be
exposed. For each method you can include an optional description and some
other settings. The method must be public. Each parameter to the method
and any return value must be serializable to XML in order for it to be exposed as
a web method.
[WebService(Description="My test service.")]
public sealed class MyWebService : WebService
{
[WebMethod(Description="Does nothing.")]
public void Foo ( ) { }
public void NotVisibleToWebServiceClients ( ) { }
[WebMethod]
public int DoSomething ( string someValue, int anotherValue ) { }
}
- What is a WSDL file? Answer
The Web Service Description Language file provides a mechanism for determining the
name of a web service and the methods available on the service. This file
is auto-generated when needed to allow clients to get information about a web service
from a web server. Technically this file defines the end points in the conversation
between a client and the server. As part of this information it contains information
about what messages (methods) it will accept along with the data they need (parameters)
and returns the response (return type and error information) that can be expected.
A full discussion of WSDL can be found in MSDN.
The file can be generated manually but is generally done dynamically by the server.
Within the WSDL file it contains all the information needed to generate a proxy
class to communicate with the web service. You can see this in action by debugging
your web service through VS. When you start the debugger it will go out to
the server and request the WSDL file and display the list of methods to you.
- You want to consume a web service in an application. How do you do it? Answer
The easiest solution is to use Add Web Reference in VS to search for and identify
the web service to connect to. If the service is in the current solution,
running on the local machine's web server or can be accessed through a web server
then VS can download the WSDL file and generate a source file that contains the
code needed to communicate with the web service.
Internally it generates a new class that derives from System.Web.Services.Protocols.SoapHttpClientProtocol.
For each web method defined on a web service it generates an equivalent class method.
The class method makes the necessary call to the web service and returns the results.
Any conversion or SOAP error handling occurs through here. It also will generate
asynchronous versions, if needed. If you select the Show All Files options
in Solution Explorer you can view this file under the web reference you added.
The class will reside in a namespace that happens to default to the name of the
server you got it from but you can change that.
For the times when you do not have direct access to the web server you can generate
the proxy class from the WSDL file directly using wsdl.exe.
This is the actual tool used by VS to generate the proxy. Through this tool
you can configure some options like the source language to use, the namespace to
use, etc. It is occasionally useful to do this rather than add a web reference
directly. With a web reference the generated proxy class can be regenerated
by VS. If you made any custom changes to the proxy file you're changes would
be lost. By generating the proxy through a WSDL file you avoid this issue
(although the file might still be regenerated by someone manually).
- You have some custom types in an assembly, A. You reference this assembly
in a web service and expose some methods that use it in assembly B. Finally
you write a client application that also uses assembly A and calls assembly B.
You get compile-time and/or runtime errors because the custom types required/sent
to the web service do not match the types that it expected. What happened? Answer
As a result of how web services are managed, all types used in a WSDL file must
be defined within the file. Therefore the WSDL contains type layout information
for each type used in the web service, including the custom types. When the
WSDL is used to generate the source file each type layout is used to generate a
new type with the same name and layout but it is ultimately different as .NET does
not use structural equivalence. Therefore, even though the web service and
the client use the exact same type assembly, the compiler sees the types as two
completely different entities. If you happen to get the issue past the compiler
then the runtime will then complain about bad types.
Prior to v2 (or perhaps v1.x) this was a serious issue. As of v2 the WSDL
tool can be used to reference additional assemblies. While examining the WSDL
file, the tool will use these additional assemblies to search for type information.
If the type information is found then the type layout defined in the WSDL will be
ignored and no new type will be generated.
To use a custom type assembly on both the web service and the client you need only
reference the type in boths assemblies during compilation. When generating
the source file from the WSDL specify the type assembly in the command line parameters.
The generated code will reference the type assembly and your client will run properly.
- You want to expose some properties to web service clients. How can you do
this? Answer
You can't, at least through the WSDL. Web services only expose web methods.
However remember that properties are really just wrappers around get and, sometimes,
set methods. Therefore you can modify the generated proxy class to expose
properties. With the property getter/setter you can invoke the web service
method(s). The client will see the methods as properties but behind the scenes
methods will still be called. If you do this then be aware that properties
should be relatively quick so you should avoid method calls whenever possible.
Caching the property value when getting it would be a good start.
- On your client you want to handle some common exceptions like invalid arguments
and invalid states when calling web services. You write the following code
but your catch block is never called even when exceptions are raised. What
is happening? Answer
public void CallFoo ( )
{
try
{
m_proxyWebService.Foo();
} catch (ArgumentOutOfRangeException e)
{ /* Report */
} catch (InvalidOperationException e)
{ /* Report */ };
}
The problem here is that .NET exceptions are not understood by SOAP. All exceptions
that occur while processing web service calls end up generating a SoapException
exception instead. Inside this exception is the InnerException
property that contains the actual exception information (if it is serializable).
To trap specific .NET exceptions you will have to handle SoapException
and grab the inner exception. A good rule of thumb is to trap SoapException,
throw the inner exception if it is understood (to hide the SOAP call altogether)
and to rethrow the exception otherwise. This does not sit well with everyone
but I am of the mind that SOAP usage should be hidden from general callers when
using a proxy class.
public void CallFoo ( )
{
try
{
m_proxyWebService.Foo();
} catch (SoapException e)
{
//If an inner exception exists
if (e.InnerException != null)
throw e.InnerException;
throw;
};
}
No solution is perfect here. By simply throwing the inner exception you might
be throwing away information about a true SOAP exception which just happens to have
an inner exception. You will probably want to be more picky about what inner
exceptions you'll return. Another disadvantage of this approach is that you
lose the original call stack that was attached to the exception. If you're
trying to hide the SOAP call then this isn't a bad thing but if you want to debug
the web service call then you have just lost the stakc information that you needed.
- You want to call a web method that can take several minutes to return. How
can you do this? Answer
On the client it is really no different than calling any other method asynchronously.
The WSDL tool generates an async method for each web method defined by the WSDL
file. If the original method is called method then three methods
are created on the proxy: method, Beginmethod and
Endmethod. method is the synchronous version.
Beginmethod starts the asynchronous call. It accepts
the input parameters and returns an IAsyncResult object that can
be used to get the results back. The Endmethod blocks
until the async operation returns and requires any output parameters, the IAsyncResult
object and returns the return value from the method.
If you call Endmethod then the call will block until the
method completes. Alternatively you can query to see if the method is complete
using the IAsyncResult object and calling one of the WaitHandle
methods of the object. The final option is to have a method called when the
async operation completes. This callback can be passed to the begin method.
It is important to always call the end method when the async operation completes
(even with the callback approach). Failure to do so will lead to unfreed memory
until the GC eventually cleans it up or potentially a memory leak if dealing with
unmanaged resources.