This chapter begins with a short history of multi-tier architecture and network operating systems, a discussion of the early days of the "network as the computer," and a discussion of where system architecture is heading today. The reason for this diversion is to understand the rationale behind Web services.
The chapter next looks at a sample Web service and walks through the process of making it accessible to the Internet as well as accessing it from a client application — both with the Visual Studio IDE and using command-line tools. From there, the chapter moves on to a key feature of Web services: the Service Repository, Discovery, and Universal Description, Discovery, and Integration (UDDI) features that enable remote programmers to correctly access Web services.
Finally, the chapter delves into more in-depth topics during discussion of the four namespaces found in the .NET Framework class library that deal with Web services and how to utilize them with Visual Basic 2008. Moving on, the chapter covers topics such as security, transactions, and the downsides of any distributed architecture (including any downsides associated with the Web services model), followed by a short discussion of where you go from here and how to get there.
A Web service is a means of exposing application logic or data via standard protocols such as XML, or, more specifically, SOAP (Simple Object Access Protocol). A Web service comprises one or more functions, packaged together for use in a common framework throughout a network. This idea is illustrated in Figure 28-1, where Web services provide access to information through standard Internet protocols, such as HTTP. By using a Web Services Description Language (WSDL) contract, consumers of the Web service can learn about the structure of the data the Web service provides, as well as all the details about how to actually consume it. A WSDL is a description of the remote interface offered from the Web service.
This simple concept provides for a very wide variety of potential uses by developers of Internet and intranet applications alike, as presented in Figure 28-1. Today, the Web services model is often the heart of the next generation of systems architecture because it is all of the following:
Architecturally neutral — Web services do not depend on a proprietary wire format, schema description, or discovery standard.
Ubiquitous — Any service that supports the associated Web service standards can support the service.
Simple — Creating Web services is easy, quick, and can be free. The data schema is human readable. Any programming language can participate.
Interoperable — Because the Web services all conform to the same standards, they can all speak to one another.
In basic terms, a Web service is an object with an XML document describing all of the methods, properties, and events sitting between the code and the caller. Any body of code written in just about any programming language can be described with this XML document, and any application that understands XML (or SOAP) over the assigned protocol (such as HTTP) can access the object. That's because the parameters you type after the function name are passed via XML to the Web service, and because SOAP is an open standard.
Microsoft has put a wrapper around all of the XML schemas that support Web services (including SOAP and WSDL), so they end up looking like .NET or COM objects. The following sections look at how the world views a Web service, and how Microsoft views Web services.
Understanding the history of the search for a decent remote method invocation (RMI) protocol is imperative to an understanding of why Web services are so important. Each of the RMI systems created before the current Web services model solved a particular set of problems, and you will see how current Web services represent the next stage in the evolution of these cross-platform boundaries to solve the problems that former technologies tried to address.
Throughout the history of computing, networking operations were largely handled by the operating system. UNIX, the networking host of early computing, featured a body of shell operations that provided remarkable user control over network operations. Personal computing was slower to catch up: Microsoft and Apple software didn't inherently support networking protocols until the mid-1990s. Third-party add-ons by Novell and Banyan were available earlier, but they were only an adjunct to the operating system. The concept of the network being the computer did not fully infiltrate the development community until the expansion of the World Wide Web.
Let's break away from networking for a minute and look at how application development evolved until now. Early time-sharing operating systems enabled several people to use the same application with its built-in data. These single-tier systems didn't allow for growth in the system's size, and data redundancy became the standard, with nightly batch jobs to synchronize the data becoming commonplace through the 1970s and early '80s.
Eventually, the opportunity presented by networks became the overriding factor in systems development, and enterprise network developers began offering the loosely termed Object Request Brokers (ORBs) on their systems: Microsoft's Transaction Server (MTS), Common Object Request Broker Architecture (CORBA), and the like. These ORBs enabled the separation of the user interface from the business logic using tightly coupled method pooling. This three-tier architecture brings you to the present in development terms, so let's step back and let networking catch up.
The HTTP protocol was born in 1990. There were several other information delivery protocols before, such as Gopher, but HTTP was different because of the extensibility of the related language, HTML, and the flexibility of the transport layer, TCP/IP. Suddenly, movement of many formats of data was possible in a stateless, distributed way. Software as a service was born.
Over the next decade, low-level protocols supported by network systems and the Internet became a staple in applications, with SMTP and FTP providing file and information transfer among distributed servers. Remote procedure calls (RPCs) took things to the next level, but they were platform specific, with UNIX implementations in CORBA and Microsoft's Distributed COM (DCOM) leading the pack.
Enterprise development took a clue from the emerging technologies in wide area network (WAN) networking and personal computing, and development for these large-scale business systems began to mature. As usage of networks grew, developers began to solve problems of scalability, reliability, and adaptability with the traditional flat-format programming model. Multi-tier development began to spread the data, processing, and user interface of applications over several machines connected by local area networks (LANs).
This made applications more scalable and reliable by accommodating growth and providing redundancy. Gradually, vendor compliance and the Java programming language provided adaptability, enabling applications to run in a variety of circumstances on a variety of platforms.
However, there was a dichotomy between the capabilities of the network and the features of the programming environment. Specifically, after the introduction of XML, there still existed no "killer app" using its power. XML is a subset of Standard Generalized Markup Language (SGML), an international standard that describes the relationship between a document's content and its structure. It enables developers to create their own tags for hierarchical data transport in an HTML-like format. With HTTP as a transport and SOAP as a protocol, still needed was an interoperable, ubiquitous, simple, broadly supported system for the execution of business logic throughout the world of Internet application development.
The hunt began with a look at the existing protocols. As had been the case for years, the Microsoft versus Sun Alliance debate was heating up among RPC programmers. CORBA versus DCOM was a source of continuing debate for developers using those platforms for distributed object development. After Sun added Remote Method Invocation to Java with Java-RMI, there were three distributed object protocols that fit none of the requirements.
Because DCOM and RMI are manufacturer-specific, it makes sense to start with those. CORBA is centrally managed by the Object Management Group, so it is a special case and should be considered separately.
RMI and DCOM provide distributed object invocation for their respective platforms — extremely important in this era of distributed networks. Both accommodate enterprisewide reuse of existing functionality, which dramatically reduces cost and time-to-market. Both provide encapsulated object methodology, preventing changes made to one set of business logic from affecting another. Finally, similar to ORB-managed objects, maintenance and client weight are reduced by the simple fact that applications using distributed objects are by nature multi-tier.
DCOM's best feature is the fact that it is based on COM, one of the most prevalent desktop object models in use today. COM components are shielded from one another, and calls between them are so well defined by the OS-specific languages that there is practically no overhead to the methods. Each COM object is instantiated in its own space, with the necessary security and protocol providers. When an object in one process needs to call an object in another process, COM handles the exchange by intercepting the call and forwarding it through one of the network protocols.
When you use DCOM, all you are doing is making the wire a bit longer. With Windows NT4, Microsoft added the TCP/IP protocol to the COM network architecture and essentially made DCOM Internet-savvy. Aside from the setup on the client and server, the inter-object calls are transparent to the client, and even to the programmer.
Any Microsoft programmer can tell you, though, that DCOM has its problems. First, because there is a customer wire transport function, most firewalls do not allow DCOM calls to get through, even though they are by nature quite benign. There is no way to query DCOM about the methods and properties available, unless you have the opportunity to get the source code or request the remote component locally. In addition, there is no standard data transfer protocol (though that is less of a problem because DCOM is mostly for Microsoft networks).
RMI is Sun's answer to DCOM. Java relies on a really neat, but very proprietary, protocol called Java Object Serialization, which protects objects marshaled as a stream. The client and server both need to be constructed in Java for this to work, but it further simplifies RMI because Java doesn't care whether the serialization takes place on one machine or across a continent. Similarly to DCOM, RMI enables the object developer to define an interface for remote access to certain methods.
CORBA uses the Internet Inter-ORB Protocol to provide remote method invocation. It is remarkably similar to Java Object Serialization in this regard. Because it is only a specification, though, it is supported by a number of languages on diverse operating systems. With CORBA, the ORB does all the work, such as finding the pointer to the parent, instantiating it so that it can receive remote requests, carrying messages back and forth, and disputing arbitration and garbage collecting. The CORBA objects use specially designed sub-ORB objects called basic (or portable) object adapters to communicate with remote ORBs, giving developers more leeway in code reuse.
At first glance, CORBA would seem to be your ace in the hole. Unfortunately, it doesn't actually work that way. CORBA suffers from the same problem web browsers do — poor implementations of the standards — which causes lack of interoperability between ORBs. With IE and Netscape, minor differences in the way pages are displayed is written off as cosmetic. When there is a problem with the CORBA standard, however, it is a real problem. Not only is appearance affected, but also network interactions, as if there were 15 different implementations of HTTP.
The principal problem of the DCOM/CORBA/RMI methods is complexity of implementation. The transfer protocol of each is based on manufacturers' standards, generally preventing interoperability. In essence, the left hand has to know what the right hand is doing. This prevents a company using DCOM from communicating with a company using CORBA.
First, there is the problem of wire format. Each of these three methods uses an OS-specific wire format that encompasses information supplied only by the operating system in question. This means two diverse machines cannot usually share information. The benefit is security: Because the client and server can make assumptions about the availability of functionality, data security can be managed with API calls to the operating system.
The second problem is the number of issues associated with describing the format of the protocol. Apart from the actual transport layer, there must be a schema, or layout, for the data that moves back and forth. Each of the three contemporary protocols makes numerous assumptions between the client and server. DCOM, for instance, provides ADO/RDS for data transport, whereas RMI has JDBC. While we can endlessly debate the merits of one over the other, we can at least agree that they don't play well together.
The third problem is knowing where to find broadly available services, even within your own network. We have all faced the problem of having to call up the COM + MMC panel so that we could remember how to spell this component or that method. When the method is resident on a server ten buildings away and you don't have access to the MMC console, the next step is digging through the text documentation, if there is any.
On a path to providing these services, we stumble across a few other technologies. While Java applets and Microsoft's client-side ActiveX technically are not distributed object invocations, they do provide distributed computing and provide important lessons. Fortunately, we can describe both in the same section because they are largely the same, with different operating systems as their backbone.
Applets and client-side ActiveX are both attempts to use the HTTP protocol to send thick clients to the end user. In circumstances where a user can provide a platform previously prepared to maintain a thicker-than-HTML client base to a precompiled binary, the ActiveX and applet protocols pass small applications to the end user, usually running a Web browser. These applications are still managed by their servers, at least loosely, and usually provide custom data transmission, utilizing the power of the client to manage the information distributed, as well as display it.
This concept was taken to the extreme with Distributed Applet-Based Massively Parallel Processing, a strategy that used the power of the Internet to complete processor-intense tasks, such as 3-D rendering or massive economic models, with a small application installed on the user's computer. If you view the Internet as a massive collection of parallel processors, sitting mostly unused, you have the right idea. An example of this type of processing is provided by United Devices (www.ud.com
).
In short, HTTP can provide distributed computing. The problem is that the tightly coupled connection between the client and server has to go, given the nature of today's large enterprises. The HTTP angle did show developers that using an industry-recognized transport method solved problem number one, wire format. Using HTTP meant that regardless of the network, the object could communicate. The client still had to know a lot about the service being sent, but the network did not.
The goal? Distributed Object Invocation meets the World Wide Web. The problems are wire format, protocol, and discovery. The solution is a standards-based, loosely coupled method invocation protocol with a huge catalog. Microsoft, IBM, and Ariba set out in 1999 to create just that, and generated the RFC for Web services.
You may notice that in reviewing the majority of the earlier services there has been little mention of language. That's because it was a problem overlooked by the foundations. Even RMI failed to recognize that you can't make everyone use the same language, even a great language.
What we need is a language-independent protocol that accommodates a standard wire transfer, protocol language, and catalog service. Java with Remote Scripting and ActiveX taught us that HTTP is the wire transfer of choice. Why? What does HTTP do that is so great? First, it is simple. The header added to a communication by HTTP is straightforward enough that power users can type it at a command prompt if they have to. Second, it doesn't require a special data protocol; it just uses ASCII text. Third, HTTP traffic can easily get through firewalls (port 80 is usually open). Finally, it is extensible. Additional headers can be added to the HTTP header for application-specific needs, and any intermediary software can just ignore it.
Now that we have a standard wire transfer protocol that we know works, we need a language and a transport mechanism. Existing languages don't really have data description functions, aside from the data management object models such as ADO. XML fits the bill because it is self-describing. The left hand doesn't need to know what the right hand is doing. An XML file transported over HTTP does not need to know the answering system's network protocol or its data description language. The concepts behind XML are so light and open that everyone can agree to support them. In fact, almost everyone has. XML has become the ASCII of the Web.
XML is important to Web services because it provides a universal format for information to be passed from system to system. We knew that, but Web services actually uses XML as the object invocation layer, changing the input and output to tightly formatted XML, making it platform and language independent.
Enter Simple Object Access Protocol (SOAP), which uses HTTP to package essentially one-way messages from service to service in such a way that business logic can interpolate a request/response pair. For your Web page to get an example, you'd make a SOAP request that would look something like this:
POST /Directory HTTP/1.1 Host: Ldap.companyname.com Content-Type: text/xml; charset="utf-8" Content-Length: 33 SOAPAction: "Some-URI"vs <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:FindPerson xmlns:m="Some-URI"> <NAME>Gates</NAME> </m: FindPerson> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
This is an HTTP page request, just like one you'd see for an HTML page except that the Content-Type
specifies XML, and there is the addition of the SOAPAction
header. SOAP has made use of the two most powerful parts of HTTP: content neutrality and extensibility. Here is the response statement from the server:
HTTP/1.1 200 OK Content-Type: text/xml; charset="utf-8" Content-Length: 66
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> <SOAP-ENV:Body> <m:FindPersonResponse xmlns:m="Some-URI"> <DIRECTORY>Employees <PERSON> <NAME>Bill Gates</NAME> <FUNCTION>Architect <TYPE>Web Services</TYPE> </FUNCTION> <CONTACT> <PHONE TYPE=CELL>123-456-7890</PHONE> <PHONE TYPE=HOME>555-111-2222</PHONE> </CONTACT> </PERSON> </DIRECTORY> </m: FindPersonResponse > </SOAP-ENV:Body> </SOAP-ENV:Envelope>
SOAP enables you to send the XML files back and forth among remote methods. It is similar to XML-RPC, a protocol developed by Dave Winer in parallel with the SOAP protocol. Both protocols provide similar structures, but the official SOAP protocol is used by Visual Basic and the entire .NET platform.
SOAP is not specific to .NET either. The SOAP Toolkit is another set of tools that Microsoft's Web Services Team provides free of charge. It contains a wonderful WSDL editor, retrofit objects for Windows 2000 and Windows NT4 servers, and more. You can find it at http://msdn.microsoft.com/webservices
.
A Web Services Description Language (WSDL) document is a set of definitions that is utilized to describe the interface of any of your Web services. Six elements are defined and used by the SOAP protocol: types, message, portType, binding, port
, and service
. Essentially adding another layer of abstraction, the purpose of WSDL is to isolate remote method invocations from their wire transport and data definition language. Once again, it is a specification, not a language, so it is much easier to get companies to agree to its use.
Because WSDL is just a set of descriptions in XML, it is not so much a protocol as a grammar. Following is the sample service contract for the HelloWorld
Web service you will be building shortly. You can see this file by visiting http://localhost/HelloWorldExample/Service.asmx?WSDL
using your Web browser after you create the examples:
<?xml version="1.0" encoding="utf-8" ?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://localhost/webservice" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://localhost/webservice"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:types> <s:schema elementFormDefault="qualified" targetNamespace="http://localhost/webservice"> <s:element name="HelloWorld"> <s:complexType /> </s:element> <s:element name="HelloWorldResponse"> <s:complexType> <s:sequence> <s:element minOccurs="0" maxOccurs="1" name="HelloWorldResult" type="s:string" /> </s:sequence> </s:complexType> </s:element> </s:schema> </wsdl:types> <wsdl:message name="HelloWorldSoapIn"> <wsdl:part name="parameters" element="tns:HelloWorld" /> </wsdl:message> <wsdl:message name="HelloWorldSoapOut"> <wsdl:part name="parameters" element="tns:HelloWorldResponse" /> </wsdl:message> <wsdl:portType name="WebServiceSoap"> <wsdl:operation name="HelloWorld"> <wsdl:input message="tns:HelloWorldSoapIn" /> <wsdl:output message="tns:HelloWorldSoapOut" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="WebServiceSoap" type="tns:WebServiceSoap"> <wsdl:documentation> <wsi:Claim conformsTo="http://ws-i.org/profiles/basic/1.0" xmlns:wsi="http://ws-i.org/schemas/conformanceClaim/" /> </wsdl:documentation> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <wsdl:operation name="HelloWorld"> <soap:operation soapAction="http://localhost/webservice/HelloWorld" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:binding name="WebServiceSoap12" type="tns:WebServiceSoap"> <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <wsdl:operation name="HelloWorld"> <soap12:operation soapAction="http://localhost/webservice/HelloWorld" style="document" /> <wsdl:input> <soap12:body use="literal" />
</wsdl:input> <wsdl:output> <soap12:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="WebService"> <wsdl:port name="WebServiceSoap" binding="tns:WebServiceSoap"> <soap:address location="http://localhost:40718/Reuters/WebService.asmx" /> </wsdl:port> <wsdl:port name="WebServiceSoap12" binding="tns:WebServiceSoap12"> <soap12:address location="http://localhost:40718/Reuters/WebService.asmx" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
This is what makes it all work. Notice that each of the inputs and outputs of the HelloWorldResponse
function is defined as an element in the schema. The .NET Framework uses this to build library files that understand how best to format the outgoing requests, so no matter what operating system develops the WSDL, as long as it is well formed according to the WSDL specification, any type of application (it doesn't necessarily need to be a .NET application) can consume it with a simple SOAP request.
In fact, IIS with the .NET Framework is set up to use the WSDL document in order to provide a great auto-generated user interface for developers and consumers to check out and test Web services. After removing the ?wsdl
from the preceding URL, you'll see a very nicely formatted documentation screen for the service. Click the function name and you will get the screen shown in Figure 28-2. This is all dynamically generated based upon the contents of the WSDL document, which is itself dynamically generated by .NET.
The WSDL document can also be expanded in order to define your own descriptions. You can use the Description
property of both the WebService
and WebMethod
attributes to provide more details for this .NET-generated test page for your XML Web services.
Building Web services with Visual Studio 2008 is incredibly easy. Microsoft has made it a cakewalk to put together a new Web service application and expose methods off that Web service.
To get started, create an ASP.NET Web Service application. Find this option by clicking File
Unlike an ASP.NET Web Application project, Visual Studio creates an .asmx
file, rather than an .aspx
file. The .asmx
file extension is short for Active Server Methods, derived from the fact that it contains methods that will be exposed through the Web service.
By default, Visual Studio creates the Web service using the code-behind model for the Web service page. In addition to the .asmx
file, Visual Studio also creates a Service.vb
file and places this file in the App_Code
folder of the project.
Open the Service.asmx
file in Visual Studio. It contains only the WebService
page directive, as shown here:
<%@ WebService Language="VB" CodeBehind="~/App_Code/Service.vb" Class="Service" %>
You use the @WebService
directive instead of the @Page
directive. The simple WebService
directive has only four possible attributes:
Class
— This required attribute specifies the class used to define the methods and data types visible to the XML Web Service clients.
CodeBehind
— Required only when you are working with an XML Web Service file using the code-behind model, this enables you to work with Web services in two separate and more manageable pieces instead of a single file. The CodeBehind
attribute takes a string value representing the physical location of the second piece of the Web Service — the class file containing all the Web service logic. In ASP.NET 2.0, it is best to place the code-behind files in the App_Code
folder, starting with the default Web Service created by Visual Studio when you initially opened the Web Service project.
Debug
— This optional attribute takes a setting of either True
or False
. If the Debug
attribute is set to True
, then the XML Web Service is compiled with debug symbols in place; setting the value to False
ensures that the Web service is compiled without the debug symbols in place.
Language
— This required attribute specifies the language used for the Web Service.
Instead of focusing on the Service.asmx
page, double-click on the Service.vb
file to open the file in the document window of Visual Studio. With the Service.vb
file in the document window, notice that the single method on the page is decorated with the <WebMethod() >
attribute. This attribute (System.Web.Services.WebMethodAttribute
) is used to tell ASP.NET to expose this particular method through the Web service.
Directly after the WebServiceBinding
attribute, place the WebService
attribute in code to define a custom namespace, which the industry recommends you always provide. The value of the namespace can be whatever you see fit; it does not have to be an actual URL, just a unique identifier:
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols <WebService(Namespace:="http://localhost/HelloWorldExample")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _ Public Class Service Inherits System.Web.Services.WebService <WebMethod()> _ Public Function HelloWorld() As String Return "Hello World" End Function End Class
Now add a new method called GoodbyeWorld
, without a WebMethod
attribute:
Public Function GoodbyeWorld() As String Return "Goodbye World" End Function
Run the project. Visual Studio will open the Service.asmx
file. By default, Web services display a test interface (see Figure 28-3) that enables you to see which methods are available and to execute them.
Notice that only the HelloWorld
method is displayed. This is the only method decorated with the WebMethod
attribute, and hence the reason why GoodbyeWorld
and all of the inherited methods on the Service
class were not displayed. Clicking the link enables you to invoke the method, as shown in Figure 28-4.
If you do this, the URL http://localhost:#####/HelloWorldExample/Service.asmx/HelloWorld
is requested, which happens to be the URL for this specific method (running with the built-in web server provided with Visual Studio). You will then see the payload of the SOAP document directly in the browser, which contains the results of the call, as shown in Figure 28-5.
That is pretty much all there is to Web services from an implementation perspective when working with the .NET Framework. The .NET Framework deals with all of the plumbing (SOAP, WSDL, and so on) discussed in the first part of this chapter on your behalf, so you only have to add properly decorated methods to the service.
Although the previous example was very easy to implement, it does not demonstrate a real-world application of Web services. Let's take a look at a more realistic example by building a Web service that provides a richer set of data from a database instead. For this example, imagine that a third-party provider hosts the site. The SQL server is behind a firewall, and the IIS server is in a demilitarized zone — a safe, though exposed, network position, as shown in Figure 28-6.
To get the data from your site to the remote site, you need to call a Web service on the remote web server from your intranet. The SOAP envelope is sent via HTTP, so the firewall allows it to pass through, and ADO.NET on the IIS server handles the actual database manipulation. The remote firewall allows database calls only from the IIS server, and the data is updated safely because of the security.
In real life, the method GetEmployees
would be local to your intranet server, and the database file would be a SQL server on a second server. Across the Internet, as shown in Figure 28-6, the Web service would be on an IIS server sitting outside the network firewall. The DLL that actually provides the data functions would be on an application server inside the firewall, and the database would again be on a separate server.
For this application, though, you need to create a Web service that exposes some of the Contact
table, as well as some of the Employee
table, from the sample AdventureWorks database, across the intranet, which will later be consumed by a Web application. Keep in mind that Web services not only expose simple values, but also a richer data set of values, such as entire tables from a data store (for example, SQL Server).
Start this example by first creating a new Web Service project in Visual Studio called WebService1.
The Visual Studio 2008 IDE shows a marked improvement from the add-ins provided for Visual Studio 6 in the SOAP Toolkit. For instance, Web services are shown as references on a project, rather than in a separate dialog box. The discovery process, discussed later, is used to its fullest, providing much more information to the developer. In short, it is nearly as easy to consume a Web service with Visual Basic as it is to use DLLs.
For simplicity, you will use Visual Studio to first create a typed DataSet
, which will be returned from the WebMethod
that you later produce. This IDE enables you to quickly and easily create the needed data access without having to dig through a lot of ADO.NET code.
Right-click the WebService1 project in the Solution Explorer and select Add New Item. From the provided menu of file options, select DataSet. Change the name of this file to MyDataComponent.xsd
. This creates an already strongly typed DataSet
on the fly. In addition, Visual Studio requests to place this file in the App_Code
folder of your solution. Confirm this request, because having it in the App_Code
folder allows for programmatic access to the DataSet
(see Figure 28-7).
Once created, the MyDataComponent.xsd
file opens itself in Visual Studio. This file appears as a blue screen in the document window. The first step is to drag and drop a single TableAdapter
onto this design surface. Once you do this, the TableAdapter Configuration Wizard opens, as shown in Figure 28-8.
The first step in the TableAdapter Configuration Wizard is establishing a data connection. If a connection is not already in place, then create one by clicking the New Connection button. Using this dialog, make a new connection to the sample AdventureWorks_Data.mdf
database, which is a SQL Server Express Edition database file. You can find this and other SQL Server 2005 samples online on the Microsoft CodePlex website (www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004
). Download the file AdventureWorksDB.msi.
Once the connection is defined in the TableAdapter Configuration Wizard, the next step of the wizard asks you to store the connection in the web.config
file, which is always a good option to choose. Choosing both of these actions will copy the AdventureWorks_Data.mdf
database file to the App_Data
folder in your project, and the connection to this database file will now be named and placed within the web.config
file of your ASP.NET Web Service project.
<connectionStrings> <add name="AdventureWorks_DataConnectionString" connectionString="Data Source=.SQLEXPRESS;AttachDbFilename=|DataDirectory| AdventureWorks_Data.mdf;Integrated Security=True;Connect Timeout=30; User Instance=True" providerName="System.Data.SqlClient" /> </connectionStrings>
Once the connection to the data store is established, click Next. In the dialog that appears, pick the command type that you want to work with. Typically, the options are working with either direct SQL commands, existing stored procedures, or stored procedures that you can create directly in the wizard. For this example, choose the first option: Use SQL Statements.
The next page in the wizard asks for the query that you want to use to load the table data. Input the following:
SELECT HumanResources.Employee.EmployeeID, HumanResources.Employee.Title, HumanResources.Employee.Gender,
HumanResources.Employee.HireDate, Person.Contact.Title AS EXPR1, Person.Contact.FirstName, Person.Contact.MiddleName, Person.Contact.LastName, Person.Contact.EmailAddress, Person.Contact.Phone FROM Person.Contact INNER JOIN HumanResources.Employee ON Person.Contact.ContactID = HumanResources.Employee.ContactID
Clicking the Next button results in a page from which you can select the methods that the wizard will generate (as shown in Figure 28-9). These are the methods used in your Web service to load data into data sets for transmission. In this case, the Fill
and GetData
methods are specified with the first two options in the dialog. In some cases, you might want to also select the last check box, which creates the additional Insert, Update
, and Delete
methods that you might want to later expose via a Web service. When you are done, click Next again to proceed to the next step in the wizard.
Figure 28-10 shows the last page of the wizard. This final page just shows the results of all the actions taken in the preceding steps.
After clicking the Finish button, note that the design surface of the MyDataComponent.xsd
file changes to reflect the data that comes from the two tables of the AdventureWorks database (see Figure 28-11).
At this point, your typed data set is now in place and ready for use by the Web service. Looking at the results on the design surface of the .xsd
file, you can see that indeed the typed MyDataComponent
data set is in place and contains a single DataTable
called DataTable1
. There is also a DataTable1TableAdapter
object with Fill
and GetData
methods in place.
Right-click Service.asmx
from within the Solution Explorer in Visual Studio and select View Code. Rename the HelloWorld
function to GetEmployees
. From here, simply retrieve data from the DataTable1TableAdapter
that was created when you created the .xsd
file earlier:
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols <WebService(Namespace:="http://www.lipperweb.com/namespace")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _ Public Class Service Inherits System.Web.Services.WebService <WebMethod()> _ Public Function GetEmployees() As MyDataComponent.DataTable1DataTable Dim da As New MyDataComponentTableAdapters.DataTable1TableAdapter Dim ds As New MyDataComponent.DataTable1DataTable da.Fill(ds) Return ds End Function End Class
If you are having trouble getting the MyDataComponent
object recognized by Visual Studio, before adding this code, be sure to build your application, and then you will find that it is recognized.
Right-click the Service.asmx
file in the Solution Explorer and select View in Browser. If there are no errors, then a simple screen listing GetEmployees
as the sole method of the service appears. Click the Service Description line. You will get a screen like the one shown earlier in Figure 28-2.
Although the Web service is in place, you really have seen only half the story. Exposing data and logic as SOAP to disparate systems across the enterprise or across the world is a simple task using .NET, and particularly ASP.NET. The other half of the story is the actual consumption of an XML Web Service into another application.
Keep in mind that you are not limited to consuming Web services only into ASP.NET applications, as shown shortly. Consuming Web services into other types of applications is not that difficult; in fact, it is rather similar to how you would consume them using ASP.NET. Remember that the Web services you come across can be consumed in Window Forms, Windows Presentation Foundation applications, mobile applications, other databases, and more. You can even consume Web services with other Web services, resulting in a single Web service made up of what is basically an aggregate of other Web services.
For this consuming application, provide a Web application called WSEmployees by creating a new ASP.NET Web Site project with that name. For this example, create this new project within your current solution: Right-click on the WebService1 solution and select Add
The only bit of magic here is the adding of a Web reference to the project using the Visual Studio IDE. As described later, you are really creating a proxy based upon the WSDL file of the service and referencing the proxy in the project, but the IDE makes this all quite simple.
To create the proxy needed by the consuming application, right-click the WSCustomers project in the Solution Explorer and select Add Web Reference from the list of options. In this form, enter the WSDL file of the Web service to which you want to make a reference. If the Web service is a .NET Web Service (with an .asmx
file extension), simply input the URL of the .asmx
file and nothing more because the wizard automatically adds ?wsdl
at the end of the input. If you are referencing a Java Web Service, then place the URL for the .wsdl
file in this wizard. In most cases, you would simply enter the URL of the service you are interested in consuming in the address bar of the Add Web Reference dialog. For this example, click the "Web Services found at this URL" link. The dialog box shown in Figure 28-12 should appear.
The service description page you see when you build your service appears in the left pane of the wizard, with .NET-specific information in the right. Click the Add Reference button at the right of the window to add this reference to your project. The service appears in a new folder called Web References within the Solution Explorer, as shown in Figure 28-13.
In making the reference, you can see that the .wsdl
file was copied over, as well as the typed DataSet, MyDataComponent
. The power of this Web services model is that it can work with the GetEmployees
method as if it were now local to your machine, when in fact it is hosted in an entirely different application.
The COM architecture continually promised "one line of code" to generate great results. Web services live up to the promise, minus the declarations. Now the only thing left to do is call the referenced Web service and pass the generated DataTable
that comes from the DataSet
. Compared to the scores of lines of XML needed to pass the DataSet
in the existing Microsoft technologies, this is a breeze.
The first step to consuming an .aspx
page is simply to make a reference to the proxy that Visual Studio created and then call the GetEmployees WebMethod
through this instantiated object. The results pulled from the GetEmployees
method are then displayed in a GridView
control, which is placed on a Web form.
You have a couple of ways to achieve this. The first method is to use an ObjectDataSource
control, which does the work of invoking the GetEmployees WebMethod
and then displaying the results in the GridView
control. (The second method, discussed a bit later, is to manually write the required code.) To work through this example, drop a GridView
and an ObjectDataSource
server control onto the design surface of the Web form. Open the smart tag of the ObjectDataSource
control and select Configure Data Source. You are then presented with the Configure Data Source Wizard.
In the first page of this wizard, uncheck the Show Only Data Components check box and select localhost.Service
from the drop-down list. Click the Next button to choose the SELECT
method for that ObjectDataSource
control to use (shown in Figure 28-14).
From the drop-down list on this page of the wizard, select GetEmployees(), returns DataTable1DataTable
and click Finish to progress to the next step of binding the GridView
control to the returned DataTable
from this ObjectDataSource
control.
Now turn your attention to the GridView
control. In configuring this control, open the control's smart tag. From the drop-down list, select ObjectDataSource1
as the data source control for this control. Note that once you do this, the GridView
control expands to include all the appropriate columns from the result set you specified earlier from the AdventureWorks database.
Now, in the same smart tag, enable paging by selecting the appropriate check boxes. The code generated by Visual Studio is shown here:
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Consuming Application</title> </head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="EmployeeID" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" InsertVisible="False" ReadOnly="True" SortExpression="EmployeeID" /> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="Gender" HeaderText="Gender" SortExpression="Gender" /> <asp:BoundField DataField="HireDate" HeaderText="HireDate" SortExpression="HireDate" /> <asp:BoundField DataField="EXPR1" HeaderText="EXPR1" SortExpression="EXPR1" /> <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" /> <asp:BoundField DataField="MiddleName" HeaderText="MiddleName" SortExpression="MiddleName" /> <asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" /> <asp:BoundField DataField="EmailAddress" HeaderText="EmailAddress" SortExpression="EmailAddress" /> <asp:BoundField DataField="Phone" HeaderText="Phone" SortExpression="Phone" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetEmployees"
TypeName="localhost.Service"></asp:ObjectDataSource> </div> </form> </body> </html>
Once the page is complete, build and run it. That's it. There is now a table in the Web form with all the data from a remote SQL Server Express Edition file that can be paged — and you did not have to write any code to achieve this functionality! The page results are shown in Figure 28-15.
Now consider doing the same thing but instead spending a little time writing some code. This is a good exercise because it offers more control over the situation (if desired), and it teaches you more about what is going on.
Create a page that includes only a GridView
server control. From here, you get at the data that comes from the Web service in the Page_Load
event, as shown in the following example:
<%@ Page Language="VB" %> <script runat="server"> Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim ws As New localhost.Service GridView1.DataSource = ws.GetEmployees() GridView1.DataBind() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Consuming Application</title>
</head> <body> <form id="form1" runat="server"> <div> <asp:GridView ID="GridView1" Runat="server" AllowPaging="True" AllowSorting="True"> </asp:GridView> </div> </form> </body> </html>
The first line of code contained in the Page_Load
event instantiates the proxy object that was created for you. The next line assigns the DataSource
property of the GridView
server control to the result set from the GetEmployees WebMethod
call. Finally, you close everything by calling the DataBind
method of the GridView
control. By compiling and running the XML Web Service, you can retrieve from the database the view of the Employees table that you created earlier from the AdventureWorks database. A returned dataset contains a wealth of information, including the following:
On the consumption side, consumers of this XML Web Service can easily use the XSD definition and the XML contained within the DataTable
within their own applications. If users are then consuming this DataTable
into .NET applications, they can easily bind this data to a GridView
and use it within their applications with minimal lines of code.
In the object-oriented world of .NET, it is quite possible to use method overloading in the code you develop. A true object-oriented language has support for polymorphism, of which method overloading is a part. Method overloading enables you to have multiple methods that use the same name but have different signatures. With method overloading, one method can be called, but the call is routed to the appropriate method based on the full signature of the request. An example of standard method overloading is illustrated in the following code listing:
Public Function HelloWorld() As String Return "Hello" End Function Public Function HelloWorld(ByVal FirstName As String) As String Return "Hello " & FirstName End Function
In this example, both methods have the same name, HelloWorld
. Which one is called when you invoke HelloWorld
depends on the signature you pass to the method. For instance, you might provide the following:
Label1.Text = HelloWorld()
This yields a result of just Hello
. However, you might invoke the HelloWorld
method using the following signature:
Label1.Text = HelloWorld("Bill Evjen")
This returns a result of Hello Bill Evjen
. As you can see, method overloading is a great feature that can be effectively utilized by your ASP.NET applications — but how do you go about overloading WebMethods?
If you have already tried to overload any of your WebMethod
s, you probably got the following error when you pulled up the Web service in the browser:
Both System.String HelloWorld(System.String) and System.String HelloWorld() use the message name 'HelloWorld'. Use the MessageName property of the WebMethod custom attribute to specify unique message names for the methods.
As this error shows, the extra step you have to take to overload WebMethod
s is to use the MessageName
property. The following bit of code shows how:
<WebMethod(MessageName:="HelloWorld")> _ Public Function HelloWorld() As String Return "Hello" End Function <WebMethod(MessageName:="HelloWorldWithFirstName")> _ Public Function HelloWorld(ByVal FirstName As String) As String Return "Hello " & FirstName End Function
In addition to adding the MessageName
property of the WebMethod
attribute, you have to disable your Web service's adherence to the WS-I Basic Profile 1.0 specification — that it would not be doing if you perform WebMethod
overloading with your Web services. You can disable conformance to the WS-I Basic Profile specification in a couple of ways. The first way is to add the <WebServiceBinding>
attribute to your code, as illustrated here:
<WebServiceBinding(ConformsTo := WsiProfiles.None)> _ Public Class MyOverloadingExample ' Code here End Class
The other option is to turn off the WS-I Basic Profile 1.0 capability in the web.config
file:
<configuration> <system.web> <webServices> <conformanceWarnings> <remove name="BasicProfile1_1" /> </conformanceWarnings> </webServices> </system.web> </configuration>
After you have enabled your Web service to overload WebMethod
s, you can see both WebMethod
s defined by their MessageName
value properties when you pull up the Web service's interface test page in the browser (see Figure 28-16).
Although the names of the WebMethod
s are distinct (based on the MessageName
property values you assigned in your code through the Web service's test page), when the developer consuming the Web service makes a Web reference to your Web service, he or she sees only a single method name available (in this example, HelloWorld
). This is evident in the IntelliSense of Visual Studio 2008 in the application consuming these methods (see Figure 28-17).
In the yellow box that pops up to guide developers on the signature structure, two options are available — one is an empty signature and the other requires a single string.
Caching is an important feature in almost every application that you build with .NET. Although many features in the .NET Framework provide different vehicles for caching, a feature of Web services in .NET enables you to cache the SOAP response sent to any of the service's consumers.
First, by way of review, remember that caching is the capability to maintain an in-memory store where data, objects, and various items are stored for reuse. This feature increases the responsiveness of the applications you build and manage. Sometimes, returning cached results can greatly affect performance.
XML Web Services use an attribute to control caching of SOAP responses — the CacheDuration
property. The following bit of code shows its use:
<WebMethod(CacheDuration:=60)> _ Public Function GetServerTime() As String Return DateTime.Now.ToLongTimeString() End Function
As you can see, CacheDuration
is used within the WebMethod
attribute much like the Description
and Name
properties. CacheDuration
takes an Integer
value that is equal to the number of seconds during which the SOAP response is cached.
When the first request comes in, the SOAP response is cached by the server, and the consumer gets the same time stamp in the SOAP response for the next minute. After that minute is up, the stored cache is discarded, and a new response is generated and stored in the cache again for servicing all other requests for the next minute.
Among the many benefits of caching your SOAP responses, you will find that your application's performance is greatly improved when you have a response that is repeatedly recreated without any change.
One of the more common forms of extending the capabilities of SOAP messages is to add metadata of the request to the SOAP message itself. The metadata is usually added to a section of the SOAP envelope called the SOAP header. Figure 28-18 shows the structure of a SOAP message.
The entire SOAP message is referred to as a SOAP envelope. Contained within the SOAP message is the SOAP body — a piece of the SOAP message that you have been working with in every example thus far. It is a required element of the SOAP message.
The one optional component of the SOAP message is the SOAP header, which is the part of the SOAP message where you can place any metadata about the overall SOAP request instead of incorporating it into the signature of any of your WebMethod
s. It is important to keep metadata separate from the actual request.
It terms of the information it contains, it could include a lot of things. One of the more common items placed in the SOAP header is any authentication/authorization functionality required to consume your Web service or to get at specific pieces of logic or data. Usernames and passwords are good examples of what you might include inside the SOAP headers of your messages.
You can build upon the sample HelloWorld
Web service presented in the default .asmx
page when it is first pulled up in Visual Studio. Name the new .asmx
file HelloSoapHeader.asmx
. Add a class that is an object representing what is to be placed in the SOAP header by the client, as shown in the following example:
Public Class HelloHeader Inherits System.Web.Services.Protocols.SoapHeader Public Username As String Public Password As String End Class
The class, representing a SOAP header object, has to inherit from the SoapHeader
class from System.Web.Services.Protocols.SoapHeader
. The SoapHeader
class serializes the payload of the <soap:header>
element into XML for you. In this example, the SOAP header requires two elements — a username and a password, both of type String
. The names you create in this class are those used for the sub-elements of the SOAP header construction, so it is important to name them descriptively.
The following code shows the Web service class that instantiates an instance of the HelloHeader
class:
<WebService(Namespace:="http://www.wrox.com/helloworld")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1, _ EmitConformanceClaims:=True)> _ Public Class HelloSoapHeader Inherits System.Web.Services.WebService Public myHeader As HelloHeader <WebMethod(), SoapHeader("myHeader")> _ Public Function HelloWorld() As String If (myHeader Is Nothing) Then Return "Hello World" Else Return "Hello " & myHeader.Username & ". " & _ "<br>Your password is: " & myHeader.Password End If End Function End Class
The Web service, HelloSoapHeader
, has a single WebMethod
— HelloWorld
. Within the Web service class, but outside of the WebMethod
itself, create an instance of the SoapHeader
class:
Public myHeader As HelloHeader
Now that you have an instance of the HelloHeader
class that you created earlier, myHeader
, you can use that instantiation in your WebMethod
. Because Web services can contain any number of WebMethod
s, it is not necessary for all WebMethod
s to use an instantiated SOAP header. You specify whether a WebMethod
uses a particular instantiation of a SOAP header class by placing the SoapHeader
attribute before the WebMethod
declaration:
<WebMethod(), SoapHeader("myHeader")> _ Public Function HelloWorld() As String ' Code here End Function
Here, the SoapHeader
attribute takes a string
value of the name of the instantiated SoapHeader
class — in this case, myHeader
.
The WebMethod
actually makes use of the myHeader
object. If the myHeader
object is not found (meaning the client did not send a SOAP header with the constructed SOAP message), then a simple "Hello World
" is returned. However, if values are provided in the header of the SOAP request, then those values are used in the returned string
value.
It is not difficult to build an ASP.NET application that makes a SOAP request to a Web service using SOAP headers. As with Web services that do not include SOAP headers, you make a Web reference to the remote Web service directly in Visual Studio.
For the ASP.NET page, create a simple page with a single Label
control. The output of the Web service is placed in this control. Following is the code for the ASP.NET page:
<%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim ws As New localhost.HelloSoapHeader() Dim wsHeader As New localhost.HelloHeader() wsHeader.Username = "Bill Evjen" wsHeader.Password = "Bubbles" ws.HelloHeaderValue = wsHeader Label1.Text = ws.HelloWorld() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Working with SOAP headers</title>
</head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" Runat="server"></asp:Label> </div> </form> </body> </html>
Two objects are instantiated. The first is the actual Web service, HelloSoapHeader
. The second, which is instantiated as wsHeader
, is the SoapHeader
object. After both of these objects are instantiated and before making the SOAP request in the application, you construct the SOAP header. This is as easy as assigning values to the Username
and Password
properties of the wsHeader
object. After these properties are assigned, you associate the wsHeader
object to the ws
object through the use of the HelloHeaderValue
property. After you have made the association between the constructed SOAP header object and the actual WebMethod
object (ws
), you can make a SOAP request, just as you would normally do:
Label1.Text = ws.HelloWorld()
Running the page produces the result shown in Figure 28-19.
Note that the SOAP request reveals that the SOAP header was indeed constructed into the overall SOAP message, as shown in the following SOAP request:
<?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <HelloHeader xmlns="http://www.wrox.com/helloworld/"> <Username>Bill Evjen</Username> <Password>Bubbles</Password> </HelloHeader> </soap:Header> <soap:Body> <HelloWorld xmlns="http://www.wrox.com/helloworld/" /> </soap:Body> </soap:Envelope>
This returns the SOAP response shown here:
<?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <HelloWorldResponse xmlns="http://www.wrox.com/helloworld/"> <HelloWorldResult>Hello Bill Evjen. Your password is: Bubbles</HelloWorldResult> </HelloWorldResponse> </soap:Body> </soap:Envelope>
Most Web services use SOAP version 1.1 for the construction of their messages. However, SOAP 1.2 became a W3C Recommendation in June 2003 (see www.w3.org/TR/soap12-part1/
). The nice thing about XML Web Services in the .NET Framework platform is that they are capable of communicating in both the 1.1 and 1.2 versions of SOAP.
In an ASP.NET application that is consuming a Web service, you can control whether the SOAP request is constructed as a SOAP 1.1 message or a 1.2 message. The next example changes the previous example to use SOAP 1.2 instead of the default setting of SOAP 1.1:
<%@ Page Language="VB" %> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Dim ws As New localhost.HelloSoapHeader() Dim wsHeader As New localhost.HelloHeader() wsHeader.Username = "Bill Evjen" wsHeader.Password = "Bubbles" ws.HelloHeaderValue = wsHeader ws.SoapVersion = System.Web.Services.Protocols.SoapProtocolVersion.Soap12 Label1.Text = ws.HelloWorld() End Sub </script>
This example first provides an instantiation of the Web service object and uses the new SoapVersion
property. The property takes a value of System.Web.Services.Protocols.SoapProtocolVersion.Soap12
to work with SOAP 1.2 specifically. With this bit of code in place, the SOAP request takes the structure shown here:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header>
<HelloHeader xmlns="http://www.wrox.com/helloworld/"> <Username>Bill Evjen</Username> <Password>Bubbles</Password> </HelloHeader> </soap:Header> <soap:Body> <HelloWorld xmlns="http://www.wrox.com/helloworld/" /> </soap:Body> </soap:Envelope>
One difference between the two examples is the xmlns:soap
namespace that is used. The difference actually resides in the HTTP header. Comparing the SOAP 1.1 and SOAP 1.2 messages, you can see a difference in the Content-Type
attribute. In addition, the SOAP 1.2 HTTP header does not use the soapaction
attribute because this is now combined with the Content-Type
attribute.
You can turn off either SOAP 1.1 or SOAP 1.2 capabilities with the Web services that you build by making the proper settings in the web.config
file, as illustrated here in this snippet of configuration code:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.web> <webServices> <protocols> <remove name="HttpSoap"/> <!-- Removes SOAP 1.1 abilities --> <remove name="HttpSoap1.2"/> <!-- Removes SOAP 1.2 abilities --> </protocols> </webServices> </system.web> </configuration>
The SOAP Toolkit provides a number of wizards to navigate most of the obstacle course required to set up a Web service, but the .NET Framework class library provides the abstract classes. The System.Web.Services
namespace provides four classes and three other namespaces that enable programmatic exposure of methods to the Web.
The System.Web.Services
namespace includes the following component classes:
The WebService
class is the base class from which all the ASP.NET services are derived, and it includes access to the public properties for Application, Context, Server, Session, Site
, and User
. ASP programmers will recognize these objects from the ASP namespace. Web services can access the IIS object model from the WebService
class, including application-level variables:
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols <WebService(Namespace:="http://www.lipperweb.com/namespace")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ Public Class Util Inherits System.Web.Services.WebService <WebMethod(Description:="Application Hit Counter", EnableSession:=False)> _ Public Function HitCounter() As String Dim HitCounter As Integer If (Application("HitCounter") Is DBNull.Value) Then Application("HitCounter") = 1 Else Application("HitCounter") = Application("HitCounter") + 1 End If HitCounter = Application("HitCounter") Return HitCounter End Function End Class
WebService
is an optional base class, used only if access to ASP.NET objects is desired. The WebMethodAttribute
class, however, is a necessity if the class needs to be available over the Web.
The WebServiceAttribute
class is similar to the WebMethodAttribute
class in that it enables the addition of the description string to an entire class, rather than method by method. We recommend adding it before the previous class declaration:
<WebService(Description:="Common Server Variables")> _ Public Class ServerVariables Inherits System.Web.Services.WebService
Instead of using WSDL in the contract to describe these services, the System.Web.Services
namespace provides programmatic access to these properties. IIS Service Discovery uses these descriptions when queried. This way, you have removed the necessity to struggle with myriad protocols surrounding Service Contract Language and SOAP.
The System.Web.Services.Description
namespace provides a host of classes that provide total management of the WSDL descriptions for your Web service. This object manages every element in the WSDL schema as a class property.
For example, the preceding discussion on the benefits of WSDL description mentioned being able to query a Web service about its methods and parameters. The System.Web.Services.Description
namespace provides methods for the discovery of methods and parameters, gathering the information from the service contract and providing it to the object model in Visual Basic code.
When working on the HTTP GET protocol (as opposed to SOAP, for instance), simply pass in the required sEmail
parameter through the use of a querystring
. You can find details about this in the Web service's WSDL description. In the successive <wsdl:message>
sections, you can find all parameter information for all three protocols, including HTTP GET (if enabled via the web.config
file):
<wsdl:message name="IsValidEmailHttpGetIn"> <wsdl:part name="sEmail" type="s:string" /> </wsdl:message> <wsdl:message name="IsValidEmailHttpGetOut"> <wsdl:part name="Body" element="tns:boolean" /> </wsdl:message>
Invoking this Web service using HTTP GET, use the following construct:
http://localserver/[email protected]
Note that HTTP GET is disabled by default because it is deemed a security risk. If you wish to enable HTTP GET for your XML Web Services, then configure it for this in the web.config
file of your Web service solution, as shown here:
<configuration> <system.web> <webServices> <protocols> <add name="HttpGet"/> </protocols> </webServices> </system.web> </configuration>
The System.Web.Services.Discovery
namespace provides access to all of the wonderful features of the .disco
files on a dynamic basis. Because Microsoft is currently trying to integrate Web services as a remoting protocol and is not pushing the public service side as much, you don't see the use of .disco
files as often in the Microsoft side of things. Your business partner might be using them, though, so this namespace proves useful. For instance, you can access the DiscoveryDocument
using the Discovery
class:
Imports System.Web.Services.Discovery ReadOnly Property DiscoveryDocument(strURL As String) As DiscoveryDocument Get DiscoveryDocument = DiscoveryClientProtocol.Discover(strURL) End Get End Property
Like the System.Web.Services.Description
namespace, the System.Web.Services.Discovery
namespace provides many tools to build a .disco
document on the fly.
All of the wire service problems solved with HTTP and SOAP are handled in the System.Web.Services.Protocols
namespace. When handling references to classes also referenced in other Web service namespaces, the System.Web.Services.Protocols
namespace proves to be a handy tool. Objects referenced by the System.Web.Services.Protocols
namespace include the following (among others):
The System.Web.Services.Protocols
namespace is particularly handy for managing the connection type by a client. A consumer of a Web service can use the HTTP GET or HTTP POST protocol to call a service, as well as the HTTP SOAP protocol. Microsoft's .NET initiative focuses on SOAP as the ultimate means of connecting disparate data sources. The System.Web.Services.Protocols.SoapDocumentMethodAttribute
class enables developers to set special attributes of a public method for when a client calls it using SOAP:
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols <WebService(Namespace:="http://www.lipperweb.com/namespace")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ Public Class Util Inherits System.Web.Services.WebService <SoapDocumentMethod(Action:="http://MySoapMethod.org/Sample", _ RequestNamespace:="http://MyNamespace.org/Request", _ RequestElementName:="GetUserNameRequest", _ ResponseNamespace:="http://MyNamespace.org/Response", _ ResponseElementName:="GetUserNameResponse")> _ WebMethod(Description:="Obtains the User Name")> _ Public Function GetUserName() '... End Function End Class
Web services impart two remarkable benefits to users — one rather obvious, the other less so. First, they replace common binary RPC formats, such as DCOM, CORBA, and RMI. Because these use a proprietary communication protocol, they are significantly less architecturally flexible than Web services. As devices utilize more and more of the Internet, platform neutrality will be a great advantage.
Less obvious but more important, Web services will be used to transfer structured business communications in a secure manner, potentially ending the hold that Sterling has on the Electronic Data Interchange (EDI) market. HTTPS with 128-bit SSL can provide the security necessary for intracompany information transfer. Furthermore, Microsoft has recently (as of this writing) released Web Services Enhancements 3.0 (WSE), as well as the Windows Communication Foundation (WCF), which enables you to easily use WS-Security and other advanced protocols to apply credentials, encryption, and digital signing to your SOAP messages in an easy and straightforward manner.
Web services are remarkably easy to deploy with Visual Basic. The key to remoting with Web services is the WSDL contract — written in the dense WSDL protocol shown earlier. IIS 5.0, 6.0, and 7.0 does that in conjunction with the .NET Framework, analyzing the VB code and dynamically generating the WSDL code for the contract.
In addition, Web services are inherently cross-platform, even when created with Microsoft products. Yes, you have heard this before, but so far it seems to be true. The standard XML schemas are centrally managed, and IBM mostly built the WSDL specification, so Microsoft seems to have been up to standard on this one.
Finally, they best represent where the Internet is heading — toward an architecturally neutral collection of devices, rather than millions of PCs surfing the World Wide Web. Encapsulating code so that you can simply and easily allow cell phones to use your logic is a major boon to developers, even if they do not know it yet.
Note that Web services are not a feature of the .NET Framework per se. In fact, Web services run fine on Windows NT4 SP6, with the SOAP Toolkit installed. You can do most anything you are doing here with VB6 and IIS 4.0.
However, the .NET Framework encapsulates the Web service protocol into objects. It is now an integrated part of the strategy, rather than an add-on. If you are currently working in a VB6 environment, look at the SOAP Toolkit (downloadable from MSDN at http://msdn.microsoft.com/webservices
), and understand that the services you build are available not only to different flavors of Windows, but also to IBM and Sun platforms.
The goal of Web services is to provide a loosely coupled, ubiquitous, universal information exchange format. Toward that end, SOAP is not the only mechanism for communicating with Web services — the HTTP GET and HTTP POST protocols are also supported by the .NET Framework. Response is via HTTP, just like normal RPCs with SOAP. This enables legacy Web applications to make use of Web services without the benefit of the .NET Framework.
The Internet is stateless by nature. Many of the techniques used for managing state in ASP.NET Web applications are the same techniques you can use within the XML Web Services built on the .NET platform. Remember that XML Web Services are part of the ASP.NET model, and both application types have the same objects at their disposal.
Therefore, just like an ASP.NET application, XML Web Services can also use the Application
object or the Session
object. These sessions can also be run in the same process as the XML Web Services application itself — out of process, using the .NET StateServer
or by storing all the sessions within SQL Server.
To use sessions within XML Web Services built on the .NET platform, you must turn on this capability within the WebMethod
attribute by using the EnableSession
property. By default, the EnableSession
property is set to False
, so to use the HTTPSessionState
object, set this property to True
, as shown here:
Imports System.Web Imports System.Web.Services Imports System.Web.Services.Protocols <WebService(Namespace:="http://www.lipperweb.com/namespace")> _ <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ Public Class Service Inherits System.Web.Services.WebService <WebMethod(EnableSession:=True)> _ Public Function SessionCounter() As Integer If Session("Counter") Is Nothing Then Session("Counter") = 1 Else Session("Counter") = CInt(Session("Counter")) + 1 End If Return CInt(Session("Counter")) End Function End Class
The EnableSession
property goes directly in the parentheses of the WebMethod
declaration. This property takes a Boolean value and needs to be set to True
in order to work with the Session
object.
Opening up a procedure call to remoting makes applications vulnerable to accidents, poor end-user implementation, and crackers. Any application design needs to include some level of security. Web services demand the inclusion of security.
Security problems with Web services fall into two categories: interception and unauthorized use. SOAP messages intercepted by crackers potentially expose private information, such as account numbers and passwords, to the public. At best, unauthorized use costs money, and at worst it wreaks havoc within a system.
Very few of the concepts discussed here are things we would like to see in the hands of those wearing the black hats. Even the simple validation service handles e-mail addresses — a valuable commodity in this world of "opt in" spamming. If you add social security or account numbers to the service, then this becomes even more of a concern. Fortunately, the wire transport of choice — HTTPS — provides a 128-bit solution.
In addition, as mentioned earlier, by using Microsoft's Web Services Enhancements (WSE) and the Windows Communication Foundation capabilities, you now can easily apply security standards such as WS-Security to your SOAP messages.
The Secure Sockets Layer (SSL) is a protocol consumed by HTTP in the transfer of Internet data from the web server to the browser. On the Web, the process works like this:
The user calls a secure Web document, and a unique public key is generated for the client browser, using the server's root certificate.
A message encrypted with the server's public key is sent from the browser.
The server can decrypt the message using its private key.
The protocol in the URI represents how HTTP would appear if it were changed to HTTPS:
<address uri="https://aspx.securedomains.com/evjen/Validate.asmx" />
The service would then make an SSL call to the server. Remember that SSL is significantly slower than HTTP, so you will suffer a performance hit. Given the sensitivity of much of the information passing over Web services, however, it is probably worth the slowdown.
You also have the option to code security into your applications. This solves different problems from SSL, and in fact you may want to combine the two services for a complete security solution.
Unauthorized access is a potential problem for any remote system, but even more so for Web services. The open architecture of the system provides crackers with all the information they need to plan an attack. Fortunately, simplicity is often the best defense. Using the NT security options already on the server is the best way to defend against unauthorized users.
You can use NTFS permissions for individual directories within an application and require users to provide a valid username and password combination if they want to access the service.
Web service security is a large area to cover. For more information, refer to the documentation included with the .NET Framework SDK.
The best approach to security is to use SSL and directory-level security together. It is slow, and at times inconvenient, but this is a small price to pay for the heightened level of security. Though this is different from the traditional role-based COM + security, it is still very effective for running information across the wire.
The Windows platform also provides for other forms of security. For instance, the Windows CryptoAPI supplies access to most of the commonly used encryption algorithms — aside from the protocols used in the Secure Sockets Layer. Digital certificates (sort of a personal form of SSL ServerCertificates
) are now rapidly becoming a powerful force in security.
There is a downside to any distributed architecture. We've covered most of them in this chapter and suggested workarounds — security, state, speed, and connectivity. Let's go over them once more to ensure that Web services are the way to go.
The key to the issue and solution of security problems is the management of client expectations. If Web services are built securely to begin with, then you won't face situations that draw concern or scrutiny. Consider the security of everything you write. It's fairly easy, and the payoff is great.
State is less of a problem in a distributed architecture because in Windows DNA, Microsoft has been saying for years that n-tier statefulness has to go. Most developers are used to the idea, but if you are not, then you need to get on the boat with the rest of us. Architect your solutions to be loosely coupled, which is what Web services are designed to do.
Web services are not made for transactional systems. If the web server at MyCompany.com
were to access a database at UPS, for example, and the connection dropped in the middle, the lock on the database would remain without giving the network system at UPS a chance to solve the problem. Web services are by nature loosely coupled. They are not designed for tight transactional integration.
A common use of Web services, communication between differing systems, prompted a number of technology architects to design several XML transaction protocols, such as 2PC. These packages provide the two systems with an understanding that the network link will remain stable.
Speed and connectivity are going to be a continuing problem until we have the ubiquitous bandwidth George Gilder talks about in his book Telecosm (Free Press, 2000). Right now, the majority of Internet devices that could really benefit from Web services — cell phones, PDAs, and the like — are stuck at the paltry 14,000 bits per second currently supported by most wireless providers.
For application development, this is a concern because when the router goes down, the application goes down. Right now, intranets continue to function when the ISP drops the ISDN. With Web services running the links to customers and suppliers, that ISDN line becomes the company lifeline. Redundancy of connections and a firm partnership with your service provider are the only solution.
The cell phone is a listening device. It listens for a call to its network address from the cell network. When it receives one, it follows some logic to handle the call. Sound familiar? This works just like the RPC architecture and will be the format for a new host of devices that listen for Web service calls over the G3 wireless network.
The first lines of the W3C XML group's charter state the following:
"Today, the principal use of the World Wide Web is for interactive access to documents and applications. In almost all cases, such access is by human users, typically working through web browsers, audio players, or other interactive front-end systems. The Web can grow significantly in power and scope if it is extended to support communication between applications, from one program to another."
New business communication will be via XML and Web services, rather than EDI and VANs. Micro-payment may actually become a reality. Scores of promises that the Internet has made since its inception can be fulfilled with Web services and XML. It won't stop there, though. The power of listening devices will bring Web services development into user-to-user markets from business-to-business ones.
It sounds far-fetched, but it is hoped that you can see how the power of Web services on .NET could make this possible. SOAP is not just about replacing the RPC architecture already out there. It is a fundamentally different way to think about the network as the platform.
This chapter looked at the need for an architecturally neutral, ubiquitous, easy to use, and interoperable system to replace DCOM, RMI, and CORBA. It discussed how Web services fill the gaps successfully because HTTP is used as the language-independent protocol, XML is its language (in WSDL) and transport mechanism, and SOAP enables you to package messages for sending over HTTP.
The chapter also described how to create and consume Web services programmatically using Visual Basic, and discussed the abstract classes provided by the .NET Framework class library to set up and work with Web Services. In particular, it looked at the WebService, WebServiceAttribute, WebMethodAttribute
, and WebServiceBindingAttribute
component classes of the System.Web.Services
namespace, in addition to the System.Web.Services.Description, System.Web.Services.Discovery
, and System.Web.Services.Protocols
namespaces.
Finally, it outlined some of the downsides to using any distributed architecture (Web services included), but it finished with an optimistic note regarding where Web services might take us in the future.