"Everything should be made as simple as possible, but not simpler." -- Albert Einstein
A typical Web service requires substantial infrastructure. If you are building services mainly for application integration, you’ll probably need to implement security and error handling. Commercial Web services require at least an additional usage tracking system. Instead of implementing this infrastructure as part of the service itself, you should consider implementing it as reusable components that can be used with any Web service. This has all the traditional benefits of code reuse including lowering development time and testing on subsequent projects. But it also has an especially important benefit in the rapidly changing world of Web services: When new standards emerge (and they will), you can replace your existing infrastructure component with one that implements the new standard thereby minimizing change to the Web service code itself. In this chapter you will learn how to leverage a powerful .NET feature named SOAP extensions to implement such reusable infrastructure components.
It’s important to first understand how Web service requests are processed and responses returned in order to understand where SOAP extensions fit within the big picture. I will first explain how request processing works then explain the details of SOAP extensions and show you how they can help in implementing reusable infrastructure.
If you open a .NET machine.config file and look for an element named httpHandlers, you’ll find a list of HTTP verbs, file extensions, and .NET types. Here’s a snippet of the httpHandlers section modified to fit within this page:
<httpHandlers>
<add verb="*" path="*.aspx"
type="System.Web.UI.PageHandlerFactory"/>
<add verb="*" path="*.asmx"
type="System.Web.Services.Protocols.WebServiceHandlerFactory,
System.Web.Services, version=1.0.3300.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
validate="false"/>
</httpHandlers>
Each <add> element maps requests for a specific file extension and HTTP verb to a specific class that will handle those requests. In this example, all requests for .aspx files (regardless of the HTTP verb used) are handled by the class System.Web.UI.PageHandlerFactory. Similarly, requests for .asmx files are handled by System.Web.Services.Protocols.WebServiceHandlerFactory which is in the System.Web.Services.dll assembly.
When a Web service request arrives, ASP.NET will instantiate a System.Web.Services.WebServiceHandlerFactory and call its GetHandler method which returns an HttpHandler object, i.e. an object that implemenents IHttpHandler. This returned object is responsible for handling the incoming request. The type of this object depends on the options you configure on your Web methods. If the Web method being requested uses ASP.NET sessions, then the returned object is a System.Web.Services.Protocols.SyncSessionHandler, otherwise it is a System.Web.Services.Protocols.SyncSessionlessHandler as shown in figure 10-1. This HttpHandler object eventually instantiates your Web service class, invokes the requested method, and returns the result as part of the HTTP response.

Figure 10‑1
Web service request processing with HttpHandlers.
If you take another look at the request processing diagram in figure 10-1, you’ll notice dashed lines indicating additional processing between the HttpHandlers and your Web service class. In fact, you can perform your own custom request processing by creating classes that inherit System.Web.Services.Protocols.SoapExtension and inserting them along the request processing path between the HttpHandler and your Web service. Such classes, called SOAP extensions, allow you to perform pre and post processing by operating directly on the SOAP request/response message at various stages during request/response processing. Their ability to process SOAP messages outside of your Web service implementation makes SOAP extensions a good choice for implementing many infrastructure services such as security and usage accounting (also known as metering).
Architecturally, SOAP extensions are fairly sophisticated because of their lifecycle and how they are initialized. Instead of exposing the entire architecture at once, I’ll peel it off one layer at a time making it easier to digest. This means you won’t have the complete picture until I’ve peeled all layer, i.e. until you read all of the following sections.
A SOAP extension is simply a class that inherits from System.Web.Services.Protocols.SoapExtension and overrides the base class’s methods. The most essential method to a SOAP extension is ProcessMessage:
Public MustOverride Sub ProcessMessage( _
ByVal message As System.Web.Services.Protocols.SoapMessage)
This method is actually called four times for each Web method invocation: Twice for the request and twice for the response. Each time ProcessMessage is called, the current request or response processing stage is indicated by the Stage property of the SoapMessage parameter. The first ProcessMessage is called is before the incoming XML is deserialized into the corresponding types. At this time, the SoapMessage’s Stage property is set to BeforeDeserialize. After incoming data is deserialized, ProcessMessage is called again with the Stage property set to AfterDeserialize. Next, the requested Web method is invoked then ProcessMessage is called for the third time before returned data (e.g. the return value and any ByRef parameters) is serialized into the response message. In this third time Message.Stage is set to BeforeSerialize. Finally, after returned data is serialized, ProcessMessage is called for the fourth time with Message.Stage set to AfterSerialize.
Note that there’s one new instance of your SOAP extension class for the entire request/response process, so for each incoming request, ProcessMessage is called four times on the same instance of your SOAP extension.
So what can you do in ProcessMessage? Well, through the SoapMessage class, you have access to all request and response information including SOAP headers, SOAP Action, and the entire SOAP message stream. You also have access to the information about the Web method used to service the request. SoapMessage has a MethodInfo property that returns a LogicalMethodInfo object. With this object you find out pretty much anything about the Web method, including its name, parameters, and any .NET attributes applied to it.
There are different things you can do at the various stages. For example, you can read input parameters (using SoapMessage.GetInParameterValue) only in the AfterDeserialize stage. Similarly, you can read the return value and ByRef parameters only in the BeforeSerialize stage.
Although SoapMessage exposes all this information as read-only properties, that is sufficient for many tasks where you do not need to alter the message itself. If you need to alter the message (perhaps you want to compress or decompress SOAP messages), you need to override one other method which I’ll explain later in this section.
Another interesting thing to know about SoapExtensions is that you can throw a SoapException from ProcessMessage which causes request processing to be aborted and a <Fault> to be sent back to the client. We’ll use this feature when implementing security to prevent unauthorized access to Web methods.
Since one instance of your SOAP extension is created and called four times for each Web method request, you will probably need a way to initialize this instance with information that will be used when you are processing the message. For example, you might want to capture the time that the request arrived and store that in a member variable. One of the SoapExtensions methods you override, Initialize, is the right place to do this kind of initialization. Initialize takes one parameter of type Object which I’ll explain in a later section. Generally speaking, Initialize is the place to do per-instance initialization. For example, this implementation creates a new GUID and stores it in a member variable named _RequestId for use in subsequent calls to ProcessMessage:
Public Overrides Sub Initialize(ByVal initializer As Object)
_RequestId = System.Guid.NewGuid().ToString()
End Sub
After you’ve built your SOAP extension you need to insert it in the request processing stream between the HttpHandler and your Web service. You have two options for doing this. To apply a SOAP extension to all Web methods on all Web services in a particular vroot, you can simply edit the vroot’s web.config and add a reference to the SOAP extension by adding the following to the webServices section:
<soapExtensionTypes>
<add type="YourExtensionTypeName,YourExtensionAssemblyName"
priority="1" group="0"/>
</soapExtensionTypes>
The type attribute references the fully qualified name of your SOAP extension class including the namespace it’s in, e.g. MyCompany.MyExtensions.TheExtension followed by a comma then the name of the assembly. For example, if your extension class is called MyNS.MyExtension and it is in an assembly called Infrastructure.dll then the configuration would be:
<soapExtensionTypes>
<add type="MyNS.MyExtension,Infrastructure"
priority="1" group="0"/>
</soapExtensionTypes>
The priority attribute indicates the relative priority of the extension within the request/response processing sequence. For example, if you have a security SOAP extension and a usage accounting SOAP extension, you will want the security one to process the request first. That way, if the request is denied, it will not show up in the usage log. To do this, you’d give the security extension a lower sequence number (by setting the priority attribute) than the accounting extension. Finally, the group attribute is also a priority-related setting with two possible values: 0 and 1. Extensions configured with group=0 belong to a higher-priority group than extensions with group=1. When determining the sequence of running extensions, ASP.NET first sorts them by group then by priority. Therefore the priority setting affects the extension’s relative priority compared to other extensions only within the same group.
In many cases, you’ll want to apply your SOAP extension to specific methods of the Web service rather than to all methods. You can do this by applying custom CLR attributes much like the XML serialization attributes you’ve seen throughout this book. First, the SOAP extension creator must create a custom attribute specific to this extension. Then developers who want to use this SOAP extension apply the custom attribute to one or more Web methods on their services.
To create a custom attribute you create a class that inherits from System.Web.Services.Protocols.SoapExtensionAttribute and override its two properties: ExtensionType and Priority. ExtensionType is a read-only property used to tell ASP.NET the SOAP extension’s class type. Priority is a read-write property used to tell ASP.NET the extension’s priority and, because it’s writable, lets the developer using your SOAP extension specify the extension’s priority. Listing 10-1 shows an example attribute implementation.
Listing 10‑1 An example custom attribute for applying a SOAP extension. (VBWSBook\Chapter10\Infrastructure\Accounting.vb).
Imports System.Web.Services.Protocols
...
<AttributeUsage(AttributeTargets.Method)> _
Public Class AccountingAttribute
Inherits SoapExtensionAttribute
Private _Priority As Integer
Private _LogResponse As Boolean
Public Sub New()
'default priority is low
_Priority = 9
End Sub
Public Overrides ReadOnly Property ExtensionType() As System.Type
Get
Return GetType(Accounting)
End Get
End Property
Public Overrides Property Priority() As Integer
Get
Return _Priority
End Get
Set(ByVal Value As Integer)
_Priority = Value
End Set
End Property
Public Property LogResponse() As Boolean
Get
Return _LogResponse
End Get
Set(ByVal Value As Boolean)
_LogResponse = Value
End Set
End Property
End Class
The AttributeUsage attribute indicates that this custom attribute can be applied to methods (not classes or parameters etc.). The default priority is set in the constructor to 9 (low). ExtensionType returns the type of a class named Accounting which is the actual SOAP extension class.
To apply the Accounting extension, a Web service developer would add a reference to the assembly that contains the extension class and then add the AccountingAttribute to their Web methods like this:
<WebMethod(), AccountingAttribute()> _
Public Function GetTemperature(ByVal ZipCode As String) As Single
Recall that VB .NET lets you omit the Attribute suffix when specifying attributes. Therefore instead of specifying AccountingAttribute() you can just specify Accounting() for short. Now when ASP.NET receives a request for the GetTemperature method, it will instantiate the Accounting SOAP extension and invoke its Initialize and ProcessMessage methods as previously explained.
I explained how you can use the Initialize method to perform per-instance initialization. Sometimes however, this is not efficient. Consider for instance the usage accounting extension which needs to log each request to a database. It would be inefficient to have to read the database’s connection string with each and every request. Therefore there’s a need to initialize certain variables once then cache these variables and use their values with each subsequent request. When you use web.config to apply a SOAP extension, you get the opportunity to perform a one-time initialization for each Web service in the vroot where web.config resides. When the Web service is invoked for the first time, ASP.NET will instantiate your SOAP extension and call its overloaded GetInitializer method passing it the type of the Web service being invoked as shown in figure 10-2 This is an opportunity for you to perform initialization work that needs to be done only once per Web service rather than once per Web method invocation. For example, you could read a database connection string from a configuration file such as web.config.
But there’s one slight problem: This instance of the SOAP extension won’t stay around for long, it’s only used to call GetInitializer. ASP.NET will create new instances of the SOAP extension to process incoming requests. Therefore, if you read a connection string from web.config, you cannot store it in a member variable because it would be lost when this instance of the extension is destroyed. So where do you cache this connection string for use in subsequent requests?
The solution to this problem is to return the connection string as the return value from GetInitializer. When the next request comes in and ASP.NET creates new instances of your extension, it will call Initialize on this new instance passing it whatever you returned from GetInitializer as shown in figure 10-2. Note that you don’t have to return a string from GetInitializer, you can return a System.Object, i.e. anything you like.

Figure 10‑2
How per-service initialization works.
In general, if your SOAP extension needs to read configuration information that does not change between Web method invocation, you should read this information and return it in GetInitializer then read it again with each request from the input object parameter in Initialize. Listing 10-2 shows an example extension that does this.
Listing 10‑2 An example extension using GetInitializer and Initialize.
Imports System.Web.Services.Protocols
...
Public Class SoapSecurity
Inherits SoapExtension
Private _ConnStr As String
Public Overloads Overrides Function GetInitializer( _
ByVal serviceType As System.Type) As Object
Dim ConnStr As String
ConnStr = System.Configuration. _
ConfigurationSettings.AppSettings.Item("SoapSecurityConnStr")
Return Config
End Function
Public Overrides Sub Initialize(ByVal initializer As Object)
_ConnStr = CType(initializer,String)
End Sub
...
The GetInitializer method in listing 10-2 will be called once for each Web service that this extension is applied to (assuming the extension is applied using web.config as explained earlier). When GetInitializer is called, a connection string is read from the web.config file and returned.
With each incoming request to the Web service, a new instance of the SoapSecurity class is created and Initialize is called. The initializer parameter (the only parameter to Initialize) will be the connection string that was originally returned from GetInitializer. The example in listing 10-2 stores this connection string in the member variable _ConnStr for use during the four calls to ProcessMessage (not shown in this listing).
There are some cases when the initialization or configuration data you read will vary for each Web method. For example, a usage accounting service might need to know whether it should log the time that the request completed (in addition to the time when the request came in). You could hardcode this information in the SOAP extension itself but that would severely limit its reusability. Since we are trying to implement reusable Web services infrastructure, such information ought to be provided through external configuration.
When you apply a SOAP extension using custom CLR attributes (rather than web.config), the extension gets a chance to perform an initialization for each Web method that it applies to. When the Web method is invoked for the first time, ASP.NET creates an instance of your Web service and calls the overloaded GetInitializer method this time passing it a LogicalMethodInfo object and a ServiceAttribute. LogicalMethodInfo gives you access to the method’s metadata such as its name and a list of parameters. ServiceAttribute is an instance of your custom attribute that was used to apply the extension to the Web method. Similar to per-service initialization, you return some object from GetInitialize and this object is passed back to Initialize for each new instance of your extension as shown in figure 10-3.

Figure 10‑3
How per-web method initialization works.
Receiving the custom attribute as a parameter allows the developer using your extension to easily pass you configuration information. Consider for example the SoapSecurityAttribute in listing 10-3.
Listing 10‑3 The SoapSecurityAttribute is used to apply a SOAP extension to Web methods. (VBWSBook\Chapter10\Infrastructure\SoapSecurity.vb).
<AttributeUsage(AttributeTargets.Method)> _
Public Class SoapSecurityAttribute
Inherits SoapExtensionAttribute
Private _Priority As Integer
Private _Permissions As String
Public Sub New(ByVal RequestedPermissions As String)
_Permissions = RequestedPermissions
_Priority = 1
End Sub
Public Property Permissions() As String
Get
Return _Permissions
End Get
Set(ByVal Value As String)
_Permissions = Value
End Set
End Property
Public Overrides Property Priority() As Integer
Get
Return _Priority
End Get
Set(ByVal Value As Integer)
'ingore this
End Set
End Property
Public Overrides ReadOnly Property ExtensionType() As System.Type
Get
Return GetType(SoapSecurity)
End Get
End Property
End Class
The interesting aspect of SoapSecurityAttribute is that its constructor takes in a string parameter and stores it in the _Permissions request variable. This means a developer using your extension can specify the permission that a Web method requires like this:
<WebMethod(), SoapSecurity("Temperature")> _
Public Function GetTemperature(...) ...
When a GetInitializer is called, an instance of SoapSecurity is passed in with its Permissions property set to “Temperature”. Using this combination of custom attributes and per-Web method initialization, developers using your extension can pass it configuration information specific to each Web method.
So far our discussion of SOAP extensions has focused on extensions that do not modify the request/response stream. Some types of SOAP extensions require the ability to modify request/response messages, for example a compression/decompression extension would need to read a compressed message and replace it with the decompressed equivalent and vice versa.
To do this in SOAP extensions you use streams. A request or response message is represented as a stream from which you can read and process message content. You then write out the new content (e.g. the compressed message) to a new stream as shown in figure 10-4.

Figure 10‑4
SOAP extensions can read request/response messages from an input stream and write modified messages to an output stream. Streams are chained: The output stream from the first SOAP extension becomes the input stream to the second SOAP extension.
So where do you get the input stream and how do you return the output stream? The answer lies in the fourth method of the SoapExtension class, namely ChainStream. This method takes in a stream object that represents the input stream and returns a stream object that represents the output stream. ChainStream is called twice per Web method invocation: Once for the request and once for the response. When this method is called on your extension, you want to save a reference to the input stream in a member variable. You also want to create a new stream and save it in a member variable and return a reference to it. For example:
Dim _InStrm As System.IO.Stream
Dim _OutStrm As System.IO.Stream
...
Public Overrides Function ChainStream( _
ByVal stream As System.IO.Stream) As System.IO.Stream
_InStrm = stream
_OutStrm = New System.IO.MemoryStream()
Return _OutStrm
End Function
In this example, I create a new System.IO.MemoryStream and use that as my output stream. Here _InStrm corresponds to the Input Stream in figure 10-4 and _Outstrm corresponds to the Output Stream.
It’s important to understand that you don’t do any stream reading or writing in ChainStream, you just setup the input and output streams then use them later when ProcessMessage is called. Once you implement ChainStream and provide an output stream as in this example, you are then responsible for writing the message out to this output stream.
Not all extensions need to modify request/response messages, therefore ChainStream is Overridable rather than MustOverride, i.e. you should not implement it unless you plan on modifying request and/or response messages.
I’ve peeled off the many layers of SOAP extension architecture and explained the four base class methods and how they work. Figure 10-5 shows a sequence diagram that summarizes the life and sequence of methods of a SOAP extension.

Figure 10‑5
Summary of SOAP extension lifetime and sequence of events.
First, an instance of the extension is created and GetInitializer is called to obtain your custom initialization object (which could be anything you want). There are two overloaded versions of GetInitializer, which one is called depends on how the extension was applied (via web.config or via a custom attribute).
Next, another instance of the extension is created to process a specific incoming request. Initialize is called on this instance passing it the object that was returned from GetInitialize. Then ChainStream is called to setup input and output streams (this method is optional). Then ProcessMessage is called before the request message is deserialized and again after the request message is deserialized. At this point, the service’s Web method is invoked and the return value and any out parameters are obtained. ProcessMessage is called again (still on instance2) before data is serialized into the response message and again after it’s serialized.
The next incoming request will result in a new instance of the SOAP extension being created (e.g. instance3) and the sequence repeats starting with the call to Initialize as shown in figure 10-5.
In addition to using SOAP extensions on the server to provide Web service infrastructure, you can also use them on the client. Later in this chapter, I’ll show an example compression/decompression SOAP extension which can be applied on the client to compress request messages and decompress response messages. On the server, the same extension can be used to decompress request messages and compress response messages. SOAP extensions work the same way on the client with the following exceptions:
· The SoapMessage parameter received by ProcessMessage is actually one of SoapServerMessage or SoapClientMessage (both inherit from SoapMessage) depending on whether the extension is being used on the client or the server.
· Because the client initiates the request and receives the response, the four stages of ProcessMessage are reversed on the client. The order on the client is: BeforeSerialize, AfterSerialize, BeforeDeserialize, AfterDeserialize. The SOAP request message is sent to the Web service between AfterSerialize and BeforeDeserialize.
Now that you understand how SOAP extensions work, you are ready to implement reusable authorization, usage tracking and compression infrastructure.
The WS-Security specification defines standards for using SOAP headers to communicate credentials, digitally signing a message, and encrypting a message. In most cases, you’re still pretty much on your own for implementing authorization (controlling access to resources based on user credentials).
Usually, authentication and authorization are insufficient because they don’t protect your service from threats such as compromised data integrity/confidentiality or replay attacks. If you are transmitting sensitive data, you’re likely to also need a digital signature and encryption mechanisms. Although you could use SSL for data encryption, WS-Security recommends the use of XML Signature and XML Encryption to digitally sign and encrypt SOAP messages. See www.LearnXmlws.com/wssecurity for more information on WS-Security[1].
The following sections show you how to leverage SOAP extensions to build an infrastructure for authorization. It’s not my intention to create a security framework for Web services. I’m merely using authorization as an example to explain SOAP extensions. I recommend using ready-made WS-Security implementations when they become available.
To understand how the security extension handles authentication and authorization you need to first understand the underlying database tables shown in figure 10-6. The Users table contains a userid and password for each registered user. UserPermissions contains a record for each permission that each user is allowed. For example, if user01 is allowed both GetWeather and GetTemperature, UserPermissions will contain two records. The first record will contain user01, Weather and the second will contain user01, Temperature.
The Sessions table is used to track currently logged-on users. When the service authenticates a user, a new record is inserted in this table with a unique SessionId (a GUID). The LastUsed field indicates the date and time that the last request was received in this session. This is useful for identifying stale sessions and removing them. Finally, AccountUsage holds a record for each incoming request as a way to track Web service usage.

Figure 10‑6
Database schema used by the Weather Web service.
Figure 10-7 shows the overall security architecture applied to a weather Web service. The Web service itself implements a LogOn and LogOff methods which delegate the real work to the SecurityMgr class (explained in the next section). The Security SoapExtension intercepts all requests (except those to LogOn and LogOff) and checks that the client is currently logged on and has the required permission (again the real work is delegated to SecurityMgr).

Figure 10‑7
How the security SoapExtension fits in with other components to implement security.
A class called SessionMgr is used to handle all database activities. This class exposes methods named LogOn and LogOff which handle creating and removing records from the Sessions table. LogOn checks the supplied user id and password against the Users table and calls CreateSession if the credentials are correct. LogOff simply deletes the session record. Listing 10-4 shows the implementation of LogOn, LogOff and CreateSession.
Listing 10‑4 Authentication functions of the SessionMgr class: LogOn, LogOff and CreateSession. (VBWSBook\Chapter10\Infrastructure\SessionMgr.vb).
Imports System.Web.Services.Protocols
Imports System.Data.SqlClient
Imports System.Configuration.ConfigurationSettings
Public Class SessionMgr
Private Const SESSION_NOT_FOUND As Integer = -1
Private Const NOT_ALLOWED As Integer = -2
Private Const ALLOWED As Integer = 1
Public Shared Function LogOn(ByVal UserId As String, _
ByVal pwd As String) As String
UserId = UserId.ToLower()
Dim cn As New _
SqlConnection(AppSettings("SoapSecurityConnStr"))
Dim Sql As String = _
"SELECT LOWER(UserID) As LUser,Password FROM Users WHERE UserId='" + _
UserId + "' AND Password='" + pwd + "'"
Dim dr As SqlDataReader
Try
cn.Open()
Catch ex As SqlException
ErrMgr.LogAndThrowEx(ex.Message, _
SoapException.ServerFaultCode, _
"SessionMgr.LogOn", "Try again later")
End Try
Dim IsValid As Boolean = False
Dim sessId As String = ""
Try
Dim cmd As New SqlCommand(Sql, cn)
dr = cmd.ExecuteReader()
If dr.Read() AndAlso _
UserId = dr("LUser") AndAlso _
pwd = dr("Password") Then
dr.Close()
sessId = CreateSession(cn, UserId)
IsValid = True
Else
IsValid = False
End If
Catch ex As SqlException
ErrMgr.LogAndThrowEx(ex.Message, _
SoapException.ServerFaultCode, _
"SessionMgr.LogOn", "Try again later")
Finally
cn.Close()
End Try
If IsValid Then
Return sessId
Else
ErrMgr.ThrowCustomEx("Invalid userid and/or password", _
SoapException.ClientFaultCode, "SessionMgr.LogOn", _
"Retry with different userid and password")
End If
End Function
Public Shared Sub LogOff(ByVal SessionId As String)
Dim cn As New SqlConnection(System.Configuration. _
ConfigurationSettings.AppSettings.Item("SoapSecurityConnStr"))
Try
cn.Open()
Catch ex As Exception
ErrMgr.LogEx("An error in SessionMgr.LogOff: " + ex.Message)
End Try
Try
Dim cmdCheck As New SqlCommand( _
"DELETE FROM SESSIONS WHERE SESSIONID='" + SessionId + "'", cn)
cmdCheck.ExecuteNonQuery()
Catch ex As Exception
ErrMgr.LogEx("An error in SessionMgr.LogOff: " + ex.Message)
Finally
cn.Close()
End Try
End Sub
Public Shared Function CreateSession( _
ByRef cn As SqlConnection, _
ByVal userId As String) As String
Dim SessionId As String = System.Guid.NewGuid().ToString
'create a session for this logon
Dim cmdKey As SqlCommand = New SqlCommand( _
"CreateSession '" + SessionId + "', '" + userId + "'", cn)
cmdKey.ExecuteNonQuery()
Return SessionId
End Function
End Class
I chose to make all of SessionMgr’s methods shared because it doesn’t need to retain any state information across method calls and shared methods are easier to call because you don’t need to instantiate an object first. In listing 10-4, LogOn first reads the security database connection string from the application’s config file (you will need to modify this connection string in web.config to point to your SQL Server database). It then connects to the database and executes a SELECT statement to find out whether the supplied user id and password exist in the Users table.
The code then checks that at least one record was returned and that the user id and password in that record are equal to what the user entered[2]. If everything checks, the code calls CreateSession passing it the database connection and the user id. CreateSession does two things: It creates a new GUID that becomes the session id then calls a stored procedure named CreateSession to insert a new record in the Sessions table. CreateSession then returns the new session id which is returned to the caller of LogOn. Although you could create a new GUID within the stored procedure, I chose to do it in code to allow for additional future processing such as digitally signing the session id.
If the supplied user id and password do not exist, LogOn uses the ErrMgr’s ThrowCustomEx method to throw a SOAP exception. ErrMgr is an infrastructure component (implemented in ErrMgr.vb) that exposes shared methods for throwing SoapExceptions and logging errors. ThrowCustomEx throws a SoapException (see Chapter 7 for more information) while LogAndThrowEx logs the exception to the application event log[3] then throws a SoapException. LogEx logs the exception without throwing it. The LogOff method simply deletes the session row from the Sessions table using the supplied SessionId.
SessionMgr also exposes an overloaded CheckPermission method (shown in listing 10-5) which first checks the supplied session id against the Sessions table then checks that the corresponding user has the requested permission.
Listing 10‑5 CheckPermission overloaded method. (VBWSBook\Chapter10\Infrastructure\SessionMgr.vb).
Public Shared Sub CheckPermission(ByVal DBConnStr As String, _
ByVal SessionId As String, ByVal ReqPermission As String)
Dim cn As New SqlConnection(DBConnStr)
Dim TimeOut As Integer = 59 'timeout in minutes
Try
cn.Open()
Catch ex As Exception
ErrMgr.LogAndThrowEx(ex.Message, _
SoapException.ServerFaultCode, _
"SessionMgr.CheckPermission", "Try again later")
End Try
Try
Dim cmdCheck As New SqlCommand("CheckPermission '" + _
SessionId + "','" + ReqPermission + _
"'," + TimeOut.ToString(), cn)
Dim results As Integer = cmdCheck.ExecuteScalar()
If results = SESSION_NOT_FOUND Then
'this is a header issue, no detail
Throw New SoapHeaderException("Session key " + _
SessionId + _
" is invalid. Your session may have timed out", _
SoapException.ClientFaultCode)
ElseIf results = NOT_ALLOWED Then
ErrMgr.ThrowCustomEx( _
"Your profile does not allow you to perform this function", _
SoapException.ClientFaultCode, _
"SessionMgr.CheckPermission", "Choose another function")
ElseIf results = ALLOWED Then
'nothing to do here
Else
'this should never occur
ErrMgr.ThrowCustomEx( _
"An error occurred while checking permissions: " + _
results.ToString() + _
" is an invalid return value from CheckPermission", _
SoapException.ServerFaultCode, _
"SessionMgr.CheckPermission", "Try again later")
End If
Catch ex As SqlException
ErrMgr.LogAndThrowEx(ex.Message, _
SoapException.ServerFaultCode, _
"SessionMgr.CheckPermission", "Try again later")
Finally
cn.Close()
End Try
End Sub
After logging on and obtaining a session id, a client must send this session id with each subsequent request. A good way to do this is through a SOAP header that the client sends with each request. Although you could also use HTTP cookies to get back the session id with each request, a SOAP header is better because it is independent of the transport protocol which means you can continue to use this architecture even if you decide HTTP is no good and switch to TCP or some other transport.
The security extension relies on a specific SOAP header class named SessionHeader which is defined in SoapSecurity.vb (part of the infrastructure project). A Web service that uses the security SOAP extension will need to declare a member of type SessionHeader to represent the SOAP header that will be sent by the client with each request. Then each secure method will need to have the SoapHeader attribute applied to it like this:
Public Class Weather
'The session id
Public sessHdr As SessionHeader
<WebMethod(), _
SoapHeader("sessHdr", _
Required:=True, _
Direction:=SoapHeaderDirection.In)> _
Public Function GetTemperature(…)
The SoapSecurity extension itself is shown in listing 10-6.
Listing 10‑6 The SoapSecurity SOAP extension (VBWSBook\Chapter10\Infrastructure\SoapSecurity.vb).
Public Structure SoapSecurityConfig
Public ConnStr As String
Public ReqPermission As String
End Structure
Public Class SoapSecurity
Inherits SoapExtension
Private _RequestedPermissions As String
Private _ConnStr As String
Public Overloads Overrides Function GetInitializer( _
ByVal methodInfo As LogicalMethodInfo, _
ByVal attribute As SoapExtensionAttribute) As Object
'this method is called once
'for each WebMethod that has the extension
Dim config As SoapSecurityConfig
config.ReqPermission = _
CType(attribute,SoapSecurityAttribute).Permissions
config.ConnStr = _
System.Configuration. _
ConfigurationSettings.AppSettings.Item("SoapSecurityConnStr")
Return config
End Function
Public Overrides Sub Initialize(ByVal initializer As Object)
Dim config As SoapSecurityConfig = _
CType(initializer, SoapSecurityConfig)
_RequestedPermissions = config.ReqPermission
_ConnStr = config.ConnStr
End Sub
Public Overrides Sub ProcessMessage( _
ByVal message As System.Web.Services.Protocols.SoapMessage)
'we don't want to catch the exception, just let it go back
If message.Stage = SoapMessageStage.AfterDeserialize Then
SessionMgr.CheckPermission( _
_ConnStr, _
SessionMgr.GetSessionId(message.Headers), _
_RequestedPermissions)
End If
End Sub
Public Overloads Overrides Function GetInitializer( _
ByVal serviceType As System.Type) As Object
'this method will not be called
End Function
End Class
The SoapSecurity extension is applied to each Web method using the SoapSecurityAttribute that was shown in listing 10-3. When GetInitializer (in listing 10-6) is called, it gets as input the SoapSecurityAttribute that was used on the specific Web method being requested. The GetInitializer implementation reads the SoapSecurityAttribute.Permissions property and stores it in config.ReqPermission. Here, config is a structure called SoapSecurityConfig with two members to hold the connection string and required permissions. GetInitializer also reads the connection string and stores it in config.ConnStr then returns config.
When Initialize is called, it gets the config structure as its input parameter. It then reads the ReqPermission and ConnStr members and stores them in the _RequesterPermissions and _ConnStr members.
Next, ProcessMessage is called four times. This method is only interested in one particular stage, that is AfterDeserialize. I picked this stage because I wanted to check permissions before the Web method is invoked so I had to use either BeforeDeserialize or AfterDeserialize. Since the incoming SOAP header is needed to get the session id and perform the authorization check, BeforeDeserialize would not work because the headers are not yet deserialized at this stage.
In AfterDeserialize stage, ProcessMessage calls SessionMgr.CheckPermission passing it the database connection string, session id (extracted from the message.Headers collection by a call to SessionMgr.GetSessionId), and the requested permissions. If all is well, processing continues normally, otherwise if the session id is invalid or if the user doesn’t have the requested permission, CheckPermission will throw an exception which will cause request processing to be aborted.
Let’s take a look at how this extension would be applied to a Web service.
Listing 10-7 shows an example Web service with a member variable named sessHdr of type SessionHeader. The Web service contains a copy of the GetCustomersDataSet and GetCustomersXml methods from chapter 9. I added the SoapHeaderAttribute to each of the Web methods and made it required (see Chapter 7 for more information on implementing SOAP headers). To apply the SOAP extension, I added the SoapSecurity attribute to each method. To specify the requested permission, I pass the SoapSecurity constructor the string “Manager” for GetCustomersDataSet method and the string “User” in the GetCustomersXml method. These strings must match the Permission field of the UserPermissions table or the user will not be allowed access to the method.
Listing 10‑7 Adding SoapHeader attribute to GetTemperature and GetWeather as part of implementing authorization. (VBWSBook\Chapter10\CustomerOrders.asmx.vb).
Imports LearnXmlWS.Web.Services.Infrastructure
<WebService([Namespace]:="http://www.LearnXmlWS.com/customerorders")> _
Public Class CustomerOrders
Inherits System.Web.Services.WebService
Public sessHdr As SessionHeader
'You need Manager permission to access this method
<WebMethod(), _
SoapSecurity("Manager"), _
SoapHeader("sessHdr", _
Required:=True, Direction:=SoapHeaderDirection.In), _
Accounting(LogResponse:=True)> _
Public Function GetCustomersDataSet() As DataSet
'code omitted for brevity
End Function
'You need User permission to access this method
<WebMethod(), _
SoapSecurity("User"), _
SoapHeader("sessHdr", _
Required:=True, Direction:=SoapHeaderDirection.In), _
Accounting(LogResponse:=True), _
SoapDocumentMethod(ParameterStyle:=SoapParameterStyle.Bare)> _
Public Function GetCustomersXml() As <XmlAnyElement()> XmlElement
'code omitted for brevity
End Function
Note that LogOn and LogOff Web methods (not shown here) do not have the SoapSecurity extension applied to them. This is because a user must first call LogOn to get the session id, so you can’t require that the request to LogOn already have a valid session id! LogOff could require a valid SessionId header but it already takes the SessionId as a parameter so that would be redundant.
Combining this SOAP extension with the LogOn and LogOff methods you have an infrastructure that provides authentication and authorization for Web services. Some of the ways you can improve on this include writing a SQL Server job to clean out old sessions and adding more sophisticated authorization logic to suit your needs. You could also implement some extra safety mechanisms such as digitally signing the session id. This would allow you to check the authenticity of the session id that you receive with each request before you hit the database for to check that the session is valid.
To give you the big picture of what we just did, figure 10-8 shows a class diagram of the Weather service implementing security. The Web service itself needs to expose LogOn and LogOff Web methods, these are not part of the infrastructure. A client must first call LogOn supplying a user id and password to get a new session id.

Figure 10‑8
The main classes used to implement authentication and authorization for a Web service.
The next SOAP extension we’ll build is for usage accounting or metering. The idea is to intercept each incoming request and log it to the AccountUsage database table with the userid, method name, and time of request. It would also be nice if the extension could optionally log the time that the response was sent back to the client. That way you also get an idea of how long each method invocation took. Listing 10-8 shows the Accounting SOAP extension implementation
Listing 10‑8 The usage accounting SOAP extension. (VBWSBook\Chapter10\Infrastructure\Accounting.vb).
Public Structure AccountingExtensionConfig
Public ConnStr As String
Public LogResponse As Boolean
End Structure
Public Class Accounting
Inherits System.Web.Services.Protocols.SoapExtension
Private _RequestId As String
Private _ConnStr As String
Private _LogResponse As Boolean
Public Overloads Overrides Function GetInitializer( _
ByVal serviceType As System.Type) As Object
'if configured in web.config, this method is called just once
Return Nothing
End Function
Public Overloads Overrides Function GetInitializer( _
ByVal methodInfo As LogicalMethodInfo, _
ByVal attribute As SoapExtensionAttribute) As Object
'if configured with attribute,
'this method is called once for each WebMethod that has the extension
'get the DB connection string
Dim config As AccountingExtensionConfig
config.LogResponse = CType(attribute, AccountingAttribute).LogResponse
config.ConnStr = _
System.Configuration. _
ConfigurationSettings.AppSettings.Item("AccountingConnStr")
'return Config because that's what we need with each request
Return config
End Function
Public Overrides Sub Initialize(ByVal initializer As Object)
'this is called once per WebMethod invocation
Dim config As AccountingExtensionConfig = _
CType(initializer, AccountingExtensionConfig)
_ConnStr = config.ConnStr
_LogResponse = config.LogResponse
_RequestId = System.Guid.NewGuid().ToString()
End Sub
Public Overrides Sub ProcessMessage( _
ByVal message As System.Web.Services.Protocols.SoapMessage)
'this is called once per stage per webmethod invocation
Try
Select Case message.Stage
Case SoapMessageStage.BeforeDeserialize
Case SoapMessageStage.AfterDeserialize
Dim params As LogRequestParams
params.DBConnStr = _ConnStr
params.RequestTime = System.DateTime.Now()
params.RequestId = Me._RequestId
params.MethodName = message.MethodInfo.Name
params.SessionId = _
SessionMgr.GetSessionId(message.Headers)
ThreadPool.QueueUserWorkItem( _
New WaitCallback( _
AddressOf AccountLog.LogRequest), params)
Case SoapMessageStage.BeforeSerialize
Case SoapMessageStage.AfterSerialize
If Me._LogResponse Then
Dim params As LogResponseParams
params.DBConnStr = _ConnStr
params.RequestId = Me._RequestId
Params.ResponseTime = System.DateTime.Now()
ThreadPool.QueueUserWorkItem( _
New WaitCallback( _
AddressOf AccountLog.LogResponse), params)
End If
End Select
Catch ex As Exception
ErrMgr.LogEx("Error in Accounting.ProcessMessage: " + ex.Message)
End Try
End Sub
End Class
This SOAP extension is applied with the AccountingAttribute you saw in listing 10-1. When GetInitializer is called, the extension reads out the LogResponse property from the input AccountingAttribute object to determine whether it should log the response time. It also reads the usage accounting database connection string from web.config. Note that this connection string could be different from the one used by the security SOAP extension, i.e. you can have different databases for security and usage accounting. Both pieces of configuration information are stored in an AccountingExtensionConfig structure and returned from GetInitializer.
When Initializer is called, it reads out the connection string and LogResponse from the input configuration object. It also creates a new GUID and stores it in the member variable named _RequestId. This unique request id will be used later to correlate a response with the corresponding request.
When ProcessMessage is called in the AfterDeserialize stage, it needs to insert a new record in the AccountUsage database table. To do this it relies on a class named AccountLog which