asp:cover
story
LANGUAGES: C#
ASP.NET VERSIONS: 1.0 | 1.1
Step by
Step
Design
and Build Message-oriented Web Services for a Service Oriented Architecture
Framework
By Jeffrey
Hasan
The
purpose of Web services in a Service Oriented Architecture (SOA) framework is
to exchange and process XML messages, not simply to act as hosts for Remote
Procedure Call (RPC)-style methods. The difference is that messages are bound
to rich and complex operations, whereas RPC-style methods simply return a
discrete result that is directly correlated to a specific set of input
parameters. For example, a message-oriented Web method will accept a stock
ticker symbol and return a detailed stock quote in response. But an RPC-style
Web method will simply return the result of an arithmetic operation on its
input parameters.
Unfortunately,
development tools such as Visual Studio.NET place a method-centric focus on Web
services that causes you to lose sight of the bigger design picture and to take
the underlying infrastructure for granted. It s very easy to build a Web
service by creating an .asmx file and then throwing together several loosely
related RPC-style Web method implementations. However, this is the wrong design
approach because such a Web service fails to provide an integrated set of
message endpoints. In simpler terms, the Web service fails to provide a
service. The correct design approach is always to think in terms of operations
and XML messages, and to consider how the Web service methods work together to
provide a service.
This
article is designed to challenge you to set aside what you have learned about
Web services development and open your mind to a different design approach
one that is based on integrated XML messages, not on RPC-style methods.
How to
Build Message-oriented Web Services
There
are six basic steps involved in building a message-oriented Web service;
following is a brief description of each.
Step
1: Design the messages and the data types. Conceptually design what the messages and data types will look
like. UML class diagrams are the best way to capture this information.
Step
2: Build the XSD schema file for the data types. Use an XML designer tool to build
the XSD schema file for all the data types that are exchanged by the Web
service methods. Visual Studio.NET s XML Designer is a good tool, but you can
use any XML Designer tool with which you are comfortable.
Step
3: Create a class file of interface definitions for the messages and data
types. The
interface definition class file provides the abstract definitions of the Web
service methods and its data types. This class file derives from the
System.Web.Services.WebService class, so it can be readily implemented in a Web
services code-behind file. The .NET Framework provides a command-line tool called
xsd.exe for generating an interface definition class file based on an XSD
schema file. This will manually generate class definitions for the data types.
You can add this class file to your Web service project and then manually
insert abstract definitions for the Web methods.
Step
4: Implement the interface in the Web service code-behind file. Your hard work in Steps 1-3 pays
off as you are now ready to implement code for the Web methods. The Web service
.asmx code-behind class derives from the System.Web.Services.WebService class
by default, as does the interface definition class file from Step 3. So you can
derive the .asmx code-behind class directly from the interface definition class
instead, and then implement code for each of the methods.
Step
5: Generate a proxy class file for clients based on the WSDL document. Web services have no reason to
exist unless they are being used by clients. In this step you generate a proxy
class file based on the Web service WSDL document so that clients know how to call
your Web service, as well as what messages and data types will be exchanged.
The wsdl.exe command-line tool will automatically generate this proxy class for
you based on the WSDL document.
Alternatively,
Visual Studio.NET will automatically generate the WSDL document for you, so no
manual work is required. VS.NET will dynamically generate the proxy class file
for you when you add a Web reference (for your Web service) to a client
project. I prefer to manually generate the proxy class file so that I can
either alter it or have it ready for clients who are using a development tool
without code-generating wizards. However, for the purposes of this article,
auto-generation of the proxy class file works just fine.
Step
6: Implement a Web service client using a proxy class file. This final step hooks a client to
your Web service. If you are using Visual Studio.NET simply add a (dynamic) Web
reference to the Web service in your client project; this will automatically
generate the proxy class file for you. This wizard will also make the necessary
adjustments to your application configuration file to record the location of
the Web service. The client essentially does nothing more than delegate method
calls out to the Web service. Valid clients include Web applications, Windows
Forms applications, Console applications, or even other Web services.
Now that
we ve reviewed the six basic steps for building a message-oriented Web service,
it s time to implement one. The remainder of the article shows you how to do
this.
Design the
XML Messages and XSD Schemas (Step 1)
XML
messages represent the operations that your Web service supports and they
correlate to implemented Web methods. XML messages don t contain implementation
logic. Instead, they simply document the name of an operation and its input and
output types. XML messages must be designed in conjunction with XSD schema
files. The best starting point is to construct a UML diagram for the operation.
Figure 1 shows a UML class diagram for the RequestQuote operation and its
associated input and output data types.
Figure 1: A UML class diagram for the
RequestQuote operation.
The UML
class diagrams will map conceptually to XSD schemas so you don t have to sketch
out any XML during the design phase unless it helps you to better visualize the
XML messages and types. For example, Figure 2 shows what the Quote type will
look like within a SOAP response (with the embedded namespaces omitted for
clarity). For design purposes, you can simplify the XML, as shown in Figure 3.
<Quote>
<Symbol>MSFT</Symbol>
<Company>Microsoft
Corporation</Company>
<DateTime>11/17/2003 16:00:00</DateTime>
<High>26.12</High>
<Low>24.68</Low>
<Open>25.49</Open>
<Last>25.15</Last>
<Change>-0.36</Change>
<PercentChange>-0.0137</PercentChange>
<Previous_Close>25.49</Previous_Close>
<High_52_Week>35</High_52_Week>
<Low_52_Week>22</Low_52_Week>
</Quote>
Figure
2: The Quote type
as it appears within a SOAP envelope.
<Quote>
</Symbol>
</Company>
</DateTime>
</High>
</Low>
</Open>
</Last>
</Change>
</PercentChange>
</Previous_Close>
</High_52_Week>
</Low_52_Week>
</Quote>
Figure
3: A simplified
view of the Quote type XML.
Clearly
it s a lot of work to sketch out even this simplified XML by hand, and it
doesn t provide any additional value beyond what the UML diagram provides. In
fact, it provides less because this sketched-out XML provides no type
information. So the message here is that for efficiency you should design your
XML messages using UML or any appropriate shorthand notation. This is the
extent of the design work that s minimally required, and you should never
shortcut this step.
Build the
XSD Schema File (Step 2)
When you
have established what your XML messages and data types will look like, it s
time to start building them. XSD schema files are the building blocks for XML
messages, so you need to design the schema files first. XSD schema files may be
coded by hand, but it s easier to use a visual designer tool, such as Visual
Studio.NET s XML Designer. To access the designer you simply add a new XSD
Schema file to a project. Visual Studio provides both a visual design view and
an XML design view. Figure 4 illustrates the visual design view for
StockTrader.xsd, which defines all the data types for this article s
accompanying StockTrader sample application (see end of article for download
details).
Figure 4: The Visual Studio.NET XML
Designer, showing the StockTrader XSD schema.
The XML
Designer includes toolbox elements that you can drag onto the surface of the
designer and then fill in, as shown in Figure 5. For example, it provides a
toolbox element for XML complex types. Simply drag this element onto the
designer and provide a name for the complex type. Then start specifying the
included types by their name and type. When you are finished defining all the
types, switch to the XML view to view the resulting XML. You can then copy and
paste the XML into a notepad file and save it with an .xsd extension.
Figure 5: The Visual Studio.NET XML Designer
Toolbox.
We don t
need to build the XML message documents by hand because they are created as
part of the WSDL document, which Visual Studio.NET will automatically generate.
But we will need to code the abstract method definitions in an interface
definition file so that the WSDL generator knows what XML messages to create.
The interface definition file contains type definitions and abstract method
definitions.
Create the
Interface Definition Class File (Step 3)
The
interface definition class file contains two important sets of information:
- Class
definitions for all custom types that are exchanged by the Web service.
- Abstract
class definitions for each operation that the Web service supports.
Figure 6
provides the code for an interface definition class file for the RequestQuote
operation and its associated types.
using System;
using
System.Web.Services;
using
System.Web.Services.Description;
using
System.Web.Services.Protocols;
using
System.Xml.Serialization;
namespace
StockTrader
{
public abstract
class StockTraderStub :
System.Web.Services.WebService
{
[WebMethod()]
[SoapDocumentMethod(RequestNamespace=
"http://www.bluestonepartners.com/schemas/StockTrader/",
ResponseNamespace="http://www.bluestonepartners.com/
schemas/StockTrader/",
Use=SoapBindingUse.Literal,
ParameterStyle=SoapParameterStyle.Bare)]
[return:
XmlElement("Quote", Namespace =
"http://www.bluestonepartners.com/schemas/StockTrader/")]
public abstract
Quote RequestQuote(string Symbol);
}
[XmlTypeAttribute(Namespace=
"http://www.bluestonepartners.com/schemas/StockTrader/")]
public class
Quote
{
public string
Symbol;
public string
Company;
public string
DateTime;
public
System.Double High;
public
System.Double Low;
public
System.Double Open;
public
System.Double Last;
public
System.Double Change;
public
System.Double PercentChange;
public
System.Double Previous_Close;
public
System.Double High_52_Week;
public
System.Double Low_52_Week;
}
}
Figure
6: The interface
definition class file for the RequestQuote operation and its associated types.
Notice
the following important points:
- The
definition file includes one stub class that encapsulates all operations, and
then any number of additional classes for the data types.
- The
interface definitions for the operations are enclosed within an abstract class
called StockTraderStub. The stub class derives from the
System.Web.Services.WebService class, so it can be implemented in a Web
service. In this listing it contains a single abstract function definition for
the RequestQuote operation.
- The
definition file contains a separate class definition for the Quote type. This
is how you are able to reference the Quote type from code-behind.
Interface
definition files (IDF) can be generated two ways:
- wsdl.exe. This command-line tool generates a
full interface definition file (including abstract classes and types) based on
a WSDL document.
- xsd.exe. This command-line tool generates
the type section only for the interface definition file based on an XSD schema
file. You can use this auto-generated file as a starting point and then
manually insert the abstract class definitions for each of the Web service
operations.
Here is
how you generate an IDF using wsdl.exe:
C:\>
wsdl /server /o:StockTraderStub.cs StockTrader.wsdl StockTrader.xsd
Here is
how you generate an IDF using xsd.exe:
C:\>
xsd StockTrader.xsd /c
Note: To
use the wsdl.exe and xsd.exe command-line tools from any directory location on
your computer you will probably need to set an environment variable that points
to the directory location of the utilities. On my computer I created a user
environment variable called PATH with a value of C:\Program Files\Microsoft
Visual Studio .NET 2003\SDK\v1.1\BIN. Alternatively, if you are using Visual
Studio.NET, then from the Programs menu group you can select Visual Studio.NET
Tools | Visual Studio.NET Command Prompt.
Implement
the Interface Definition in the Web Service (Step 4)
When the
interface definitions are in place the last remaining step is to implement them
in the Web service code-behind. The first step is to derive the Web service
class file from the interface definition, and the second step is to override
the abstract methods, as shown in Figure 7.
// Step 1
(Before View): Implement the StockTraderStub class
[WebService(Namespace
=
"http://www.bluestonepartners.com/schemas/StockTrader")]
public class
StockTraderService : StockTraderStub
{
// Contains abstract methods (not shown)
}
// Step 2
(After View): Override and implement each of the
abstract class methods
[WebService(Namespace
=
"http://www.bluestonepartners.com/schemas/StockTrader")]
public class
StockTraderService : StockTraderStub
{
public override
Quote RequestQuote(string Symbol)
{
//
Implementation code goes here
}
}
Figure
7: Derive the Web
service .asmx code-behind class from the generated interface definition class
(StockTraderStub).
You need
to set namespace names for both the Web service class and the interface
definition classes. I usually include all classes within the same namespace,
but there is no rule about this. If you do use different namespaces then in the
Web service class file you ll need to import the namespace for the interface
definition classes.
At this
point everything is in place to complete the implementation of the Web service
methods. All operations and types are fully described and ready to be
referenced from the Web service class file. Figure 8 shows an example
implementation of the PlaceTrade Web method, which places a trade order and
returns the trade details in a custom object type named Trade.
[WebMethod()]
[SoapDocumentMethod(RequestNamespace=
"http://www.bluestonepartners.com/schemas/StockTrader/",
ResponseNamespace="http://www.bluestonepartners.com/
schemas/StockTrader/",
Use=SoapBindingUse.Literal,
ParameterStyle=SoapParameterStyle.Bare)]
[return:
XmlElement("Trade", Namespace =
"http://www.bluestonepartners.com/schemas/StockTrader/")]
public override
Trade PlaceTrade(string Account,
string Symbol, int Shares, System.Double
Price,
TradeType tradeType)
{
Trade t = new
Trade();
t.TradeID =
System.Guid.NewGuid().ToString();
t.OrderDateTime
= DateTime.Now.ToLongDateString();
t.Symbol =
Symbol;
t.Shares =
Shares;
t.Price =
Price;
t.tradeType =
tradeType;
// Initialize
the Trade to Ordered, using the
TradeStatus enumeration
t.tradeStatus =
TradeStatus.Ordered;
// Code Not
Shown: Persist trade details to the database by
account number and trade ID, or to a message
queue
for later processing
// Code goes
here
return t; //
Return the Trade object
}
Figure
8: The PlaceTrade
Web method.
Assuming
that you ve followed the steps so far your Visual Studio.NET solution explorer
will look like Figure 9.
Figure 9: The Visual Studio.NET Solution
Explorer showing the StockTrader Web service.
Implement
the Web Service Consumer (Steps 5 and 6)
The
difficult part of the development is done. By now you should have a good
understanding of how to approach the development process for message-oriented
Web services. Hopefully you now have a better understanding of the variety of
moving parts that work together to power a Web service. Visual Studio.NET
allows you to take shortcuts in the development process, but you need to avoid
temptation and do the manual work that is required to create well documented
Web services.
We ll
close out this article with the final step of hooking the Web service to a
client consumer. Figure 10 shows the Visual Studio.NET Solution Explorer as it
appears when you add a consumer project to the same solution file as the
StockTrader Web service. Note that this is done for convenience, to make
debugging the projects simpler. In reality, the Web service and the consumer
projects would be located on separate servers and likely in different domains.
Figure 10: The Visual Studio.NET Solution
Explorer showing the StockTrader Web service and the Web service consumer
project.
The
consumer project contains a proxy class for the Web service that is
automatically generated when you add a Web reference from the consumer to the
Web service. The proxy class file derives from
System.Web.Services.Protocols.SoapHttpClientProtocol, and it provides
synchronous and asynchronous invocation mechanisms for each of the Web service
operations. It also provides class definitions for the Web service types, just
like the interface definition file. The proxy file does not include abstract
methods (it only includes implemented methods), so you don t have to implement
every method that the proxy class file provides. In addition, the consumer
class doesn t need to derive from the service proxy class you simply create
instances of the proxy class as needed. (This article does not provide specific
instructions for how to create the consumer project, so please refer directly
to the code samples that accompany this article.)
Figure
11 shows a form-based implementation of the consumer that allows you to receive
stock quotes and place trades. Figure 12 shows a sample of the implementation
code behind the Get Stock Quote button.
Figure 11: A consumer application for the
StockTrader Web service.
private void
btnQuote_Click(
object sender, System.EventArgs e)
{
// Create an
instance of the Web service proxy
StockTraderProxy
serviceProxy =
new StockTraderProxy();
// Retrieve the
Web Service URI from app.config
serviceProxy.Url
=
ConfigurationSettings.AppSettings["remoteHost"];
// Call the Web
service to request a quote
Quote q =
serviceProxy.RequestQuote(this.txtSymbol.Text);
// Display the
Quote results in the form
this.lblCompany.Text
= q.Company;
this.lblSymbol.Text
= q.Symbol;
this.lblTradeDateTime.Text
= q.DateTime;
this.lblLastTrade.Text
= q.Last.ToString();
this.lblPreviousClose.Text
= q.Previous_Close.ToString();
this.lblChange.Text
= q.Change.ToString();
}
Figure
12: Web service
consumer code.
Notice
that the client code references a configuration element called
<remoteHost> that provides the URI for the StockTrader Web service. It
should be entered into the project s web.config file, as shown in Figure 13.
This concludes our discussion of how to build a basic message-oriented Web
service.
<?xml
version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add
key="remoteHost" value="http://localhost/
StockTrader/StockTrader.asmx"/>
</appSettings>
</configuration>
Figure
13: The web.config
file for the Web service consumer.
Conclusion
The
purpose of Web services is to exchange and process XML messages, not to act as
simple endpoints for remote procedure calls. In this article we studied a
six-step process for designing and building a message-oriented Web service from
scratch. These steps are:
1)
Design
the messages and the data types.
2)
Build
the XSD schema file for the data types.
3)
Create
a class file of interface definitions for the messages and data types.
4)
Implement
the interface in the Web service code-behind file.
5)
Generate
a proxy class file (for clients) based on the WSDL.
6)
Implement
a Web service client using a proxy class file.
The goal
of this article is to help you rethink your approach to Web services design so
you can start developing the type of message-oriented Web services that fit
into a Service Oriented Architecture framework.
The
sample code referenced in this article is available for download.
Jeffrey
Hasan is president
of Bluestone Partners, Inc., an IT solutions company based in Orange County, CA
(http://www.bluestonepartners.com).
Jeff is an experienced enterprise architect and .NET developer, is the coauthor
of several books and articles on .NET technology, including Performance Tuning and Optimizing ASP.NET Applications (Apress, 2003), and is the author
of the upcoming book Expert Service Oriented
Architecture in C#: Using the Web Services Enhancements 2.0 (Apress, 2004). Contact Jeff at mailto:jeffh@bluestonepartners.com.