Just as there are design decisions to be made when you build a web service, there are design decisions related to how to use a web service. One of the most obvious is handling exceptions.
In an ideal scenario, whenever you call a web service, some type of application logic is executed performing the desired task according to the request. However, code often has bugs . SOAP takes this fact into account and defines the response that a web service should use when an error occurs. This error takes the form of a SOAP message, and it is known as a SOAP Exception .
Requests to ASP.NET Web services that generate an exception will return a SOAP exception. Within the System.Web.Services.Protocols namespace is a class named SoapException . Clients using .NET can wrap try...catch blocks around calls to web services and catch SOAP Exceptions as instances of a SoapException class. Let's look at an example. The following is a simple web service (written using VB.NET) that can generate a runtime 'divide by zero' error:
<%@ WebService Class="ExceptionExample" %> Imports System.Web.Services Public Class ExceptionExample <WebMethod()> Public Function Divide(a As Integer, b As Integer) As Integer Return a / b End Function End Class
Using Visual Studio .NET or wsdl.exe , you can build a proxy class. Let's name it ExceptionExample , deploy it to a web application's bin directory, and call the ExceptionExample Web service. Using VB.NET, you could write:
<Script runat="server"> Public Sub Page_Load(sender As Object, e As EventArgs) Dim example As New ExceptionExample() lblDiv1.Text = example.Divide(6,2) lblDiv2.Text = example.Divide(5,0) End Sub </Script> The result of 6 divided by 2 is: <asp:label id="lblDiv1" runat="server" /> <br> The result of 5 divided by 0 is: <asp:label id="lblDiv2" runat="server" />
If you execute this code, the statement example.Divide(6,2) is valid, and should execute. However, the statement example.Divide(5,0) results in a divide-by-zero error. Figure 20-8 the result:
The code raises an unhandled exception on line 6 in our call to Divide(5,0) . The exception that is bubbled up and displayed in the ASP.NET error page is of type SoapException . Let's make a few modifications to the code to handle this exception:
<%@ Import Namespace="System.Web.Services.Protocols" %> <Script runat="server"> Public Sub Page_Load(sender As Object, e As EventArgs) Dim example As New ExceptionExample() Try lblDiv1.Text = example.Divide(6,2) Catch err As SoapException lblDiv1.Text = "Unable to compute..." End Try Try lblDiv2.Text = example.Divide(5,0) Catch err As SoapException lblDiv2.Text = "Unable to compute..." End Try End Sub </Script> The result of 6 divided by 2 is: <asp:label id="lblDiv1" runat="server" /> <br> The result of 5 divided by 0 is: <asp:label id="lblDiv2" runat="server" />
Unlike an ASP/VBScript combination, ASP.NET supports structured error-handling (so no more On Error Resume Next ) thanks to the CLR. In our code we've made a few modifications to catch exceptions: we've added a namespace, System.Web.Services.Protocols , which contains the SoapException class and have added try...catch blocks around the code. Now, if an exception of type SoapException occurs, we can catch the exception and display a meaningful result.
When you run the modified code, you get the following result, as shown in Figure 20-9:
Another design decision is whether to use SOAP headers to send additional data with the web service. In the previous chapter, we examined how to support SOAP headers on the server. Now, let's look at how to use SOAP headers with the proxy.
The following is a simple ASP.NET Web service (written in VB.NET) that implements a SOAP header:
<%@ WebService Class="UsingSoapHeaders" %> Imports System.Web.Services Imports System.Web.Services.Protocols Public Class MySoapHeader : Inherits SoapHeader Public Value As String End Class Public Class UsingSoapHeaders Public sHeader As MySoapHeader <WebMethod(), SoapHeader("sHeader")> _ Public Function GetValueOfSoapHeader() As String Return sHeader.Value End Function End Class
Here, GetValueOfSoapHeader returns the value of the SOAP header that the caller presents .
The information for SimpleSoapHeader is described in the WSDL for the service, as the application using the web service will require knowledge of the SoapHeader , in order to call the service:
<s:element name="MySoapHeader" nillable="true" type="s0:MySoapHeader" /> <s:complexType name="MySoapHeader"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="Value" nillable="true" type="s:string" /> </s:sequence> </s:complexType>
This WSDL uses an XML Schema to define a SOAP header, MySoapHeader . The proxy generator (Visual Studio .NET or wsdl.exe ) will then create a class MySoapHeader with a single member variable Value .
The following is the VB.NET code generated by Visual Studio .NET for the WSDL preceding the web service:
Namespace Simple <WebServiceBindingAttribute(Name:="UsingSoapHeadersSoap") [Namespace]:="http://tempuri.org/")> Public Class UsingSoapHeaders Inherits SoapHttpClientProtocol Public MySoapHeaderValue As MySoapHeader Public Sub New() MyBase.New Me.Url = "http://localhost/.../SoapHeaders_vb.asmx" End Sub <SoapHeaderAttribute("MySoapHeaderValue")> _ <SoapDocumentMethodAttribute("http://tempuri.org/GetValueOfSoapHeader", Use:=SoapBindingUse.Literal, ParameterStyle:= SoapParameterStyle.Wrapped)> Public Function GetValueOfSoapHeader() As String Dim results() As Object = Me.Invoke("GetValueOfSoapHeader", _ New Object(0) {}) Return CType(results(0),String) End Function End Class <XmlRootAttribute([Namespace]:="http://tempuri.org/", IsNullable:=true)> Public Class MySoapHeader Inherits SoapHeader Public Value As String End Class End Namespace
The proxy class contains a MySoapHeader subclass, which has a member variable, Value . The proxy class also contains a member variable, MySoapHeaderValue , which is used to set the SOAP header.
Let's look at how to use this proxy, along with its SOAP header to send additional data with the web service request. The following is a simple ASP.NET page (written in VB.NET) that uses the proxy:
<%@ Import Namespace="Simple" %> <script runat="server"> Public Sub Page_Load(sender As Object, e As EventArgs) ' Create a new instance of the UsingSoapHeaders ' proxy class used to call the remote .asmx file Dim soapHeaderExample As New UsingSoapHeaders() ' Create a new instance of the mySoapHeader class Dim myHeader As New MySoapHeader() ' Set the value of myHeader myHeader.Value = "Sample Header Text" ' Set the MySoapHeader public member of the ' UsingSoapHeaders class to myHeader soapHeaderExample.MySoapHeaderValue = myHeader ' Get the result of the call Dim result As String result = soapHeaderExample.GetValueOfSoapHeader() span1.InnerHtml = result End Sub </script> <font size=6>The value of the SOAP header is: <font color="red"> <span id="span1" runat="server"/></font></font>
In the Page_Load event handler, we create a new instance of the web service proxy, soapHeaderExample . We also create an instance of MySoapHeader that represents the SOAP header ( myHeader ). The Value field of myHeader is set to Sample Header Text .
Next, we set myHeader to the soapHeaderExample member variable MySoapHeaderValue . Finally, we call the GetValueOfSoapHeader Web service. The MySoapHeader class instance myHeader is serialized as a SOAP header for the message and is sent along with the request. When the call completes, the value that was sent as a SOAP header is displayed.
Using SOAP headers is easy. The WSDL of the service describes what the SOAP header is, and the proxy generation tool turns the XML description found in the WSDL into a .NET class that you can program with. SOAP headers are very powerful since they allow you to pass out-of- band data (data that is part of the request, but doesn't belong as part of the method marked as a web service). For example, to send a user ID with each request, you obviously wouldn't want to design each of the methods in the web service to accept that user ID as a parameter. It would be much easier to simply make use of a SOAP header.
This example is relatively simple, but it's enough to allow us to demonstrate how we could build some meaningful applications that use SOAP headers. Later, you'll see a real-world example of how to use SOAP headers. Specifically, you'll see how you can use SOAP headers to send authentication details with each request. Using SOAP headers for authentication is obviously a custom implementation of authentication and authorization, and so you should start by reviewing the security features that are already available.