Saturday, 29 December 2007

Improve web service efficiency by using SoapDocumentMethod(OneWay = true)

Sometimes we have a long running process in a web method but we don't want our client application to wait until the process is finished. We can decorate our web method with this attribute:
[SoapDocumentMethod(OneWay = true)]

This setting tells web service client not to wait for the web server to finish the process of the web method. This is ideal for logging client events because client application doesn't care if the web server is sucessfull with doing logging.

Friday, 28 December 2007

Custom SoapExtension, SoapHeader and Web Method authentication

Currently I am working on a project which involves Web Service and Windows Client. The web service will be public and we are going to give the Windows client application to our suppliers across the Internet. The communication between client and our server is through Web Service.

To make our Web Service secure, login is required. Users provide their user name and password in the initial Web Service call through a login web method. In the subsequent calls, the user name and password are provided all the time. By doing this an anounimous user will fail when making a call to our secured methods.

This makes me think how I am going to transfer login information. In my previous project for another client, I used to concatenate user name and password into one string and pass to each web method through an extra parameter. And we validate the user in each web method. I feel this is tedious and not quite right. Fortunately I came across SoapHeader and SoapExtention when I read the book: .NET Framework 2.0 Distributed Application Development published by Microsoft Press. And I realized this is what I need to implement web method authentication.

According to SOAP specification, a SOAP message contains a Header, an Envelope, and a Body element. The Header is optional, but if included, it's inside the Envelope element and usually contains authentication information.

.NET we can have our custom header class which inherits from SoapHeader class. Here is my example:

public class ServiceAuthHeader : SoapHeader
{
private string userName;

public string UserName
{
get { return userName; }
set { userName = value; }
}
private string password;

public string Password
{
get { return password; }
set { password = value; }
}
}


In the client side consuming code, we must pass user data through Header as below:

RemoteService.RemoteService service = new RemoteService.RemoteService();
RemoteService.ServiceAuthHeader authHeader = new RemoteService.ServiceAuthHeader();
authHeader.UserName = userName;
authHeader.Password = password;
service.ServiceAuthHeaderValue = authHeader;

DataSet ds = service.GetMasterData();

In the Web Service class, we declare a public ServiceAuthHeader property.

private ServiceAuthHeader authSoapHeader;
public ServiceAuthHeader AuthSoapHeader
{
get { return authSoapHeader; }
set { authSoapHeader = value; }
}


And decorate the web method with SoapHeaderAttribute, as below:

[SoapHeader("AuthSoapHeader")]
[WebMethod]
public DataSet GetMasterData()
{
string userName = string.Empty;
if (AuthSoapHeader != null)
{
userName = AuthSoapHeader.UserName;
// then do business with userName.
}
}

Note there is nothing related with Authentication so far. Authentication is implemented through SoapExtension as discussed below.

SoapExtension privides a powful way of allowing developers to add additional functionalities to standard web methods. In another project we used SoapExtension to provide data compression for communication between a Windows Mobile device and a web server through GPRS network.

We simply inherit our custom extension class from SoapExtension as below:
public class AuthSoapExtension : SoapExtension

There are several abstract methods from SoapExtension, we are only interested in ProcessMessage(SoapMessage message) method. This method gives developers a chance to handle the SOAP message in each stage. There are four stages during the processing of a SOAP message in the web server: BeforeSerialize, AfterSerialize, BeforeDeserialize, and AfterDeserialize. The Stage property of message parameter indicates which stage the current message is in. The Header object is available in the AfterDeserialize stage because the SOAP message is deserialized into .NET objects.

The following code is used to get user information from header and validate.

public override void ProcessMessage(SoapMessage message)
{
if (message.Stage == SoapMessageStage.AfterDeserialize)
{
ServiceAuthHeader header = message.Headers[0] as ServiceAuthHeader;
if (header != null)
{
bool valid = ServiceValidation.ValidateUser(header.UserName, header.Password);
if (!valid)
{
throw new Exception("Invalid username or password.");
}
}else{
throw new ArgumentNullException("No ServiceAuthHeader provided in SoapMessage.");
}
}
}

To apply this custom soap extension to our web method, we need another attribute inherits from SoapExtensionAttribute.

[AttributeUsage(AttributeTargets.Method)]
public class AuthExtensionAttribute : SoapExtensionAttribute
{
private int _priority;

public override Type ExtensionType
{
get { return typeof(AuthSoapExtension); }
}

public override int Priority
{
get
{
return _priority;
}
set
{
_priority = value;
}
}
}


Then in our web method, we decorate it with this attribute:

[SoapHeader("AuthSoapHeader")]
[WebMethod]
[AuthExtension]
public DataSet GetMasterData()

The decoration tells the web method to use our custom AuthSoapExtension. Each time this web method is called, the ProcessMessage method in AuthSoapExtension is triggered, and the username and password information is retrieved from our custom SoapHeader and validated against a database.