<$BlogRSDUrl$>

Wednesday, March 09, 2005

.NET Web Services using WSE 

.NET Web Services using WSE
.NET makes creating or using a web service easy, but it's not always exactly as described in the documentation.

This article covers using the Microsoft Web Services Enhancements (WSE) in Visual C# .NET. I'm assuming you're relatively "au fait" with C# and the .NET framework, and are interested in writing web services.
There are several ways of writing web services available to programmers using Microsoft's Visual Studio .NET environment, and several programming languages to choose from. Whilst variety may be the spice of life, it does also make it a little difficult to know where best to begin. To compound such problems, the web services scene is changing fast. No sooner have you got your head around using the .NET framework and the web service support available, than yet another set of libraries comes out which purports to "make life easier" in one way or another. I am, of course, referring to the latest
Web Services Enhancements library shipped by Microsoft.
Whenever a new class library comes out, I always ask myself whether it's really worth the effort of learning it, or will the library either (a) just add a layer of unnecessary complication to the current development project, or (b) be superseded by another "later and greater" class library in the not too distant future. In order to answer such questions you are immediately landed in a Catch-22 situation, since you have to learn something about the class library before you can decide whether to "go the whole hog" and learn it fully. The purpose of this article is to make you familiar enough with the WSE library that you can answer such questions for yourself.

Looking at requirements
One of my current architectural tasks is to look at turning a classic n-tier web-based application that is driven by a browser GUI front-end into a programmatically distributed system. That is, one instance of a server (I like to think of it as a "server atom") can programmatically talk to another server atom somewhere on the Internet (that's correct: not intranet but Internet). I could design our own, private and proprietary data exchange mechanism, operating (probably, but not necessarily) over HTTP. I'm inherently suspicious of anything that's private and proprietary, on the basis that it's almost guaranteed that someone, somewhere will have been confronted with the same task. In any case, it looks like distributed web services are a good bet for a first look at meeting the requirements, but how to communicate between the distributed web services? I could use a fully proprietary design but I dismissed that idea straight away. How about XML RPC after all, I'm looking primarily at an RPC-type environment? Seems fine for connectivity but... the XML documents that I send and receive are once again a proprietary format for XML RPC. The Simple Object Access Protocol (SOAP) addresses this proprietary XML RPC format quite nicely by providing a standard XML format for the messages. So it looks like SOAP is the way to go, at least for the moment. Let's explore this path a little further.
The kinds of operations I need to support are synchronous and asynchronous calls of remote services, with either type of call possibly returning data either immediately as a result of the call or at some future point.
I conclude that I need to use SOAP for exposing web services. As the data I return potentially contains sensitive information, I need to look at security of the data transfer. I need to look at the possibility of sending binary data back (e.g. a bcp bulk copy utility query result from a database in native database format).
This brings me to the heart of this article: Microsoft recently released the WSE library to extend the web service support already provided in .NET. WSE provides me with an implementation of three recently released web service specifications: WS-Security, WS-Attachments and WS-Routing. The question facing me now was whether using WSE would help me in reaching my goal.

What WSE provides
Web Services Enhancements, as the "Enhancements" part of the name implies, are extensions to the SOAP specification. That always makes me nervous. The word "extension" implies "non-standard", and non-standard is something I definitely want to avoid. However, the facilities provided by WSE use the SOAP extension model and are also based in some cases on existing Internet draft documents (e.g. WS-Attachments is based on work on the DIME Internet draft). There are other standards out there too. For example, the XACML security specification drafted under the good auspices of OASIS provides a standard for applying a security policy in a web services environment. The parallel to XACML in the Microsoft world is the WS-Policy specification.
The three WS facilities provided by WSE come under the general auspices of Microsoft's Global XML Web Services Architecture, commonly abbreviated to GXA. It is intended that GXA is an umbrella concept that will use one or more of the WS facilities plugged together by an architect to achieve a final design goal. For example, WS-Routing provides the fundamental services that WS-Security can use to ensure privacy of information. Both WS-Routing and WS-Security are component services of the GXA. WSE is an implementation of the WS-Security, WS-Routing and WS-Attachments specifications. I won't look at how WSE achieves what it does, but will simply focus on how you can use WSE.
The outline development plan for this article is as follows:
Write a web service using WSE to expose, say, a factorial calculation
Write a client to consume that web service
Re-work (1) and (2) to use security
Re-work (1) and (2) to send an arbitrary binary attachment, both to and from the web service
Re-work (1) and (2) to send a binary attachment that is encrypted.

Getting started
This section will look at providing a simple web service to expose a factorial calculation and how to configure the server-side web service to use WSE. Once that is done, I will generate a client program to call the web service. The client program will be generated in part by using a proxy-generating tool in Visual Studio .NET. The generated code will need slight modification to use WSE and I'll detail what needs to be done there for the client to use WSE. By the end of this section you should be up and running with a web service and client that use WSE but don't do much with WSE at all. After all that, you'll see in the next section how to start getting WSE involved in helping meet the requirements of a secure web service that can transmit and receive arbitrary length binary data.
First of all, you'll need to ensure you have IIS up and running. Secondly, you'll need to
install WSE.
The particular version of WSE I'm using is WSE 1.0 SP1. While you're at it, you may as well get hold of the
WSE Settings Tool for Visual Studio .NET.
Start Visual Studio .NET and create a new Visual C# ASP.NET Web Service project. The name I've given the web service in this article is

http://localhost/FactorialService
A short pause ensues while Visual Studio .NET creates a virtual directory called FactorialService on IIS and marks it as an application. The source files are then placed within this virtual directory. Whilst this is fine for development, you may wish to exclude the source files when deploying the web service to your production web server!
From Solution Explorer, select the Service1.asmx file and rename it to something a little more meaningful. I chose to rename this service to FactorialService by right clicking on the Service1.asmx file and choosing "Properties". In the property inspector, I changed the file name from "Service1.asmx" to "FactorialService.asmx". Now switch to the Class View window in Visual Studio and expand the tree until you can see Service1. Right-click on this and rename it to Factorial.

Server-Side
Browsing the C# source, you should find that a commented-out "HelloWorld" method has been provided, prefixed with the [WebMethod] attribute. To modify the web service to provide a factorial function:
Add two constants for max and min factorial range somewhere in the class

private const int minFact = 0;
private const int maxFact = 12;

Replace the HelloWorld commented-out method with the following method (leaving the [WebMethod] attribute in place)
public int Calc(int n)
{
int factVal = -1;
// use -1 as out-of-range indicator
if(n >= minFact && n <= maxFact)
{
// factorial by iteration
factVal = 1;
// 0! and 1! are both 1 by
// definition
for(int factCnt = 2; factCnt
<= n; ++factCnt)
{
factVal *= factCnt;
}
}
return factVal;
}

As far as programming goes, that's it! You now have a web service. To hook into WSE, however, you will need to change the application's Web.config file.
In order for your web service to hook into WSE, you need to add some XML to the Web.config file to tell IIS how to invoke WSE. Select the Web.config file in Solution Explorer and add the required XML entries. This is shown in Figure 1.
Figure 1
The element is used with the "type" attribute, which is set to point to WSE by its assembly name. For display purposes, I have had to wrap the "type" attribute so you can see what the value should be. In the actual Web.config file, of course, the type attribute should be one line with no newlines in it. I have omitted the rest of the Web.config file in Figure 1 as it remains as-is.
You should be able to build the web service now.

The client
I'll create a real basic console application in C# to talk to the web service in order to demonstrate that you don't need any heavyweight client code to use a web service. First of all I'll generate a basic client and use Visual Studio .NET tools to generate the required proxy class to talk to the web service. I'll then show how you need to modify the generated proxy file to use WSE. That will complete the basic web service and client configuration.
In Visual Studio .NET generate a new Visual C# Console Application. I called mine FacClient. You can place the client in the same solution as the web service if you wish, but I chose to keep my client completely separate from the web service project in its own "solution" space. Skeleton code will be generated for you. The first thing you'll need to do is a reference to WSE for your client program. You do this by going to the Solution Explorer and right-clicking over References. From the context menu that opens, select "Add Reference". The resulting dialog is shown in Figure 2, which also shows Microsoft.Web.Services in the list of .NET assemblies.
Figure 2
If the assembly does not appear in the list, you will have to use the "Browse" button on the dialog. By default, the DLL is located at:

%Program Files%\Microsoft
WSE\v1.0.2312\Microsoft.Web.
Services.dll

Once you have located the assembly, click on "Select" then on "OK". In order to use the assembly in your client code you will need to either explicitly reference each instance using the full path name or place the following using directive at the top of your client program: using Microsoft.Web.Services;. You can change the class and file name (as detailed in the section "Server-Side Coding") if you wish, but I chose to leave the class name as Class1 (and filename as Class1.cs) for this simple client.
Next we need to generate the proxy class to talk to the web service. You do this by right clicking over References in Solution Explorer again - but this time selecting "Add Web Reference" from the context menu, which results in a dialog being displayed. You need to enter the URL that points to the FactorialService.asmx file. Figure 3 shows the URL typed in at the top and the resultant output.
Figure 3
Click on the "Add Reference" button at the bottom of the dialog. This adds a proxy class to your project and some more references. Figure 4 shows the "Class View" panel indicating that two proxy classes have been generated in the localhost namespace.
Figure 4
The proxy class Factorial is the standard SOAP HTTP proxy class. The other proxy class, and the one we're more interested in, is FactorialWse. The Wse is appended to the name to show that it is using WSE. The difference between the two comes down to the fact that the basic SOAP proxy class derives from the SoapHttpClientProtocol class and the WSE proxy derives from WebServicesClientProtocol, which provides the hooks into WSE.
Note: the WSE documentation talks about editing the generated proxy class file and changing the inheritance. This documentation point is out of date and you do not need to do this any more. Simply use the "Wse" version of the proxy class.
In order to use the proxy class add the using directive using FacClient.localhost; somewhere near the top of your client program source and implement Main as follows:

static void Main(string[] args)
{
try
{
FactorialWse fs =
new FactorialWse();
int val = 6;
int fac = fs.Calc(val);
Console.WriteLine(
"Web service called
successfully!");
Console.WriteLine(
"Factorial of {0} is {1}",
val, fac);
}
catch(Exception e)
{
Console.WriteLine(
"Error with web service: "
+ e.Message);
}
}

You should be able to build and run the client and, all being well, you should find out that the factorial of 6 is 720. Nothing too earth-shattering about that, I agree but what you have done is successfully implemented a web service and client that use WSE albeit at a very basic level. On this foundation, I'm now going to look at using the facilities provided by WSE to see if it can meet my original requirements, which, after all, is the point of the exercise.
Web services enhancements and WS-specifications
The WSE .NET library provides an implementation of the WS-Security, WS-Attachments. I'm going to look at WS-Security and WS-Attachments.
There is no point pursuing the use of WSE if a reasonable degree of data privacy is not achievable using WS-Security. The security mechanism that WS-Security can use is not fixed. For demonstration purposes I will use a simple "shared secret" symmetric encryption process. The major advantage of WS-Security over other security implementations is that WS-Security is implemented on the SOAP packet, whereas other security mechanisms take place at the transport layer (e.g. HTTPS, SSH) and provide a point-to-point encoding. With a distributed web service you don't necessarily know the route a SOAP packet will take, so WS-Security applies security measures at the SOAP packet level rather than the transport level. The WSE documentation contains instructions for building a WSE WS-Security web service that can decrypt a SOAP message and a WSE WS-Security client that can encrypt a SOAP message. I'm going to use the instructions provided in the WSE documentation as a starting point in part because it fulfils one-half of my encryption requirement and in part because the documentation is incorrect in several places and I will show you the correct version in this article. Let's go for building the web service first, then writing the client and generating the proxy class using Visual Studio.NET.
I'm going to modify the FactorialService and client so that the client can send an encrypted request to the web service to generate the factorial of the encrypted value. Add a reference to Microsoft.Web.Services and System.Security by choosing Add Reference from the context menu on the References node in the Solution Explorer window. You can add both references in one hit in the Add Reference dialog by first choosing "Microsoft.Web.Services" and clicking "Select", followed by choosing "System.Security" and clicking "Select" again. Click on "OK" to complete adding the references. Add the following using directives somewhere near the top of your C# source file:

using Microsoft.Web.Services;
using Microsoft.Web.Services.Security;
using System.Security.Cryptography;
using System.Security.
Cryptography.Xml;
using System.Security.Permissions;

Notice that the WSE documentation in Point 9.b is missing the using System.Security.Permissions; directive.
In order to decrypt the incoming message, you need to provide a DecryptionKeyProvider in the form of a class. WSE will use your class to decrypt the SOAP message at the point of reception and then pass the decrypted message to your web service. As far as your main web service is concerned, it knows nothing about the encryption of the call it is all handled by WSE and your own custom class. Point 10 in the WSE documentation describes the method of adding a class to be used for DecryptionKeyProvider, but it's unfortunately out-of-date in stating that your class needs to implement the IDecryptionKeyProvider interface. This is no longer true your class should inherit from a WSE base class called DecryptionKeyProvider. Given that the example in the WSE documentation calls its own class DecryptionKeyProvider, if you also inherit from a class called DecryptionKeyProvider then you have a circular inheritance loop and the code will no longer compile. In summary, you need to change the inheritance and also call the class something different to the one in the WSE documentation. I called my class DecKeyProv and declared it as:

public class DecKeyProv :
DecryptionKeyProvider

The security attributes for this class are the same as in the WSE documentation, although the WSE documentation version will not compile due, firstly, to the missing using directive (dealt with above), and secondly in part due to the fact that the method as given does not compile!
If you do try to compile the method GetDecryptionKey as given in the WSE documentation you will get an error to the effect of "not all control paths return a value". The easiest way of fixing this is to add return null; just after the close of the foreach loop as the last statement in the method that will fix the error. Now on to the warning: a warning is issued about your GetDecryptionKey method hiding the base class GetDecryptionKey method. What you need is to override the base class implementation so the declaration for your GetDecryptionKey method becomes:

public override DecryptionKey
GetDecryptionKey(...).

This is what an outline of the class and method should look like:
public class DecKeyProv :
DecryptionKeyProvider
{
public override
DecryptionKey
GetDecryptionKey(
string algorithmUri,
KeyInfo keyInfo)
{
// body as per WSE document
// (plus corrections)
// or exactly as in the code
// sample for this article
}
}

The DecKeyProv class can be added to the FactorialService namespace. For ease of writing, I placed the class in the same source file as the main web service, although you could place it in a separate file if you wish.
XML web service life just wouldn't be the same without editing an XML file somewhere or other, so I'll look next at what you need to do to the Web.Config file to get the WS-Security functionality up and running.
The first thing you need to configure is the server-side to use the WS-Security implementation in WSE, so it's back to the Web.config file. I'm working on the basis that you've already added the standard WSE blurb as detailed in "Server-Side Configuration" above. A couple of things need to be in place in the Web.config file to cater for security. The first is to add a that gives a name to the WSE library (we'll call it microsoft.web.services as per the WSE documentation). The second step involves using the microsoft.web.services section you've just defined to pull in your security class for decryption. You can add this directly beneath (not inside!) the element. Both these steps are shown in Figure 5.
Figure 5
The "type" attribute of the section tag (in ) is exactly the same as the "type" attribute as shown in Figure 1. I didn't re-format it here for display purposes to emphasise the fact that the type attribute should be on one line in the Web.config file.
All the pieces are now in place and you should be able to build the web service successfully.

Client Security
Using the earlier client, add a reference to System.Security via Solution Explorer. Add the following using directives to the top of the source file:
using Microsoft.Web.Services.Security;
using System.Web.Services.Protocols;
using System.Security.Cryptography;
using System.Security.
Cryptography.Xml;

The procedure for encrypting your request follows a simple pattern:
Generate an EncryptionKey object
Modify the SOAP proxy class's requestContext to use the EncryptionKey
Call the service
The WSE documentation for "Encrypting a SOAP message Using a Shared Secret" gives an implementation of a method called GetEncryptionKey, which uses TripleDES as the encryption algorithm. We'll run with this version, so all you need do is add the GetEncryptionKey method to your client code. I made a slight modification to the visibility of the GetEncryptionKey that is made private in the WSE documentation. I turned it into a public static method to facilitate ease of use from my console application's Main method.
The complete procedure for the client console app is reproduced here to show how the above three steps are executed. The code forms the body of Main.

// proxy class
FactorialWse fs = new FactorialWse();

// encryption key
EncryptionKey key =
Class1.GetEncryptionKey();

// modifying requestContext to encrypt
SoapContext requestContext =
fs.RequestSoapContext;
EncryptedData enc =
new EncryptedData(key);
requestContext.Security.Elements.Add(
enc);
requestContext.Timestamp.Ttl = 30000;
//30 secs, avoid replay hacks

// actually calling the web service
int val = 6;
int fac = fs.Calc(val);
Console.WriteLine("Factorial of {0}
is {1}", val, fac);

WS-Attachments
I need to send arbitrary binary data, and I may need to send it encrypted too, as it may contain personal or commercially sensitive information. WSE supports arbitrary transfer of binary attachments using DIME.
To enable the web service to send an attachment using DIME, add the following using directive to FactorialService.asmx: using Microsoft.Web.Services.Dime;
To actually send a binary attachment all you need do is add a DimeAttachment object to the Attachments collection of the SOAP response context. The DimeAttachment object wraps the binary data you wish to send. A code sample illustrates sending a GIF image file (roughly taken from the WSE documentation):

// 1. Get response context
SoapContext ctx =
HttpSoapContext.ResponseContext;

// 2. Create the dime attachment
// object, wrapping the image file
DimeAttachment att =
new DimeAttachment(
"image/gif", // MIME type
TypeFormatEnum.MediaType,
// media type enum
@"c:\myimage.gif");
// filename of image

// 3. Add the DimeAttachment object
// to the response
ctx.Attachments.Add(att);

That's it. In terms of our factorial web service, you can add a method that will return some binary data, call it, say, GetBinData. For the sake of consistency with the WSE documentation, let's stick to sending a binary image file for now. Add a public web services method to the factorial service like so:
[WebMethod]
public void GetBinData()
{
// body as above for sending GIF
// image via DIME
}

To obtain some test data, just copy any GIF image you have locally to the root directory of the C drive and rename it to myimage.gif so that the web service can locate it. The client needs to be able to get the binary attachment from the SOAP message, a process that turns out to be relatively simple. The client looks at the Attachments collection of the ResponseSoapContext object exposed from the client's WSE proxy class. The WSE documentation shows a simple C# client application displaying the image in a picture window. The WSE sample application meets all the requirements of proving that WSE's WS-Attachments is working fine, so I won't produce a separate client for this article.
Summary
This article has looked at a readily available implementation of the two web services standards (WS-Security and WS-Attachments), and has hopefully given you a starting point from which you can expand your web service implementations on the .NET platform using WSE.

Comments: Post a Comment

This page is powered by Blogger. Isn't yours?