Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

638 C H A P T E R 1 5 W E B S E R V I C E S

The Web Services Namespaces

Five namespaces within the .NET Framework are directly related to Web services development:

System::Web::Services is the primary namespace for Web services development. It consists of classes required for Web services creation.

System::Web::Services::Configuration consists of classes that configure how Web services are created using ASP.NET.

System::Web::Services::Description provides classes to programmatically interface with the WSDL.

System::Web::Services::Discovery provides classes to programmatically discover Web services on a Web server.

System::Web::Services::Protocols defines the protocols for transmitting data to and from the client and Web service over the network.

Most of the time when you develop Web services, you can be almost completely ignorant of the preceding namespaces. Normally, all you will need when implementing a Web service is two attributes,

WebServiceAttribute and WebMethodAttribute, and an optional class, WebService. You use this class as a base class from which to inherit your Web service. You can find all three in the

System::Web::Services namespace.

You use the System::Web::Services::Protocols namespace as well, but only indirectly within auto-generated code created when you add a Web reference.

A Simple Web Service

Enough theory—let’s look at some code. In this example, you’ll create an overly simplified Web service that finds a zip code based on city and state. It’s so oversimplified that it finds the zip code only for two city and state combinations. In truth, it really doesn’t matter what the internal workings of a Web service are, as they’re just (in the case of this book) standard C++/CLI classes. What is special is the ability to access these classes over a network.

The process of creating a Web service is very easy. The first step is the same as that of any other project: Select the appropriate template (in this case, the ASP.NET Web Service template) to start building your Web service and give it a name. As you can see in Figure 15-1, I gave the project the name FindZipCode.

Once the New Project Wizard finishes, you’re left with (believe it or not) a fully functioning “Hello World” Web service. Okay, let’s modify the “Hello World” service so that it provides zip code– finding functionality.

The first thing I usually do with the template is delete the generated Web service *.asmx, *.cpp, and *.h files. Then I add a new ASP.NET Web service with a more appropriate name. If you like the default name generated, then you can go ahead and use that one. In the case of this example, I actually like the default, FindZipCodeClass, so I won’t go through the delete process.

The code generated by the Web service wizard uses the standard two-file format of C++/CLI (finally, a C++/CLI template that is done correctly!). Well, actually, to be more accurate, the code generated is a three-file format, since the template provides an .asmx file as well as the .cpp and .h files. An .asmx file is a Web service file that defines the class where the methods of the service reside.

Web services are not fully supported by C++/CLI and the only way to implement them is to precompile the source. In other languages, such as C# and Visual Basic .NET, there would be two additional attributes: the Language attribute, which specifies the language of the associated code, and the Codebehind attribute, which specifies the source file for the Web service. These other attributes allow the Web service to be compiled at runtime.

C H A P T E R 1 5 W E B S E R V I C E S

639

Figure 15-1. Selecting the ASP.NET Web Service template

The first file you should look at is FindZipCodeClass.asmx. In almost all cases, you will not change the contents of this file. As you can see in Listing 15-1, the file contains a single WebService directive containing a Class attribute that specifies the name of the associated class with this .asmx file.

Listing 15-1. FindZipCodeClass.asmx

<%@ WebService Class=FindZipCode.FindZipCodeClass %>

The next file of interest in this simple example is the FindZipCodeClass.h file, which contain the definitions of the methods that make up the Web service. Listing 15-2 shows the final version of FindZipCodeClass.h.

Listing 15-2. FindZipCodeClass.h

#pragma once

using namespace System; using namespace System::Web;

using namespace System::Web::Services;

namespace FindZipCode {

[WebServiceBinding(ConformsTo=WsiProfiles::BasicProfile1_1, EmitConformanceClaims = true)]

[WebService(Namespace="http://procppcli.net", Description = "Zip code retrieval service")]

public ref class FindZipCodeClass : public WebService

{

640 C H A P T E R 1 5 W E B S E R V I C E S

public:

FindZipCodeClass()

{

InitializeComponent();

}

protected:

~FindZipCodeClass()

{

if (components)

{

delete components;

}

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent()

{

}

#pragma endregion

public:

[WebMethod(Description = "Get the zip code from city and state")] int GetZip(String ^city, String ^state);

};

}

I removed the comments to save space. I also removed the redundant namespace qualifying because using namespace System::Web::Services does this for you. But you should probably leave the comments in and update them to reflect your Web service’s functionality. Whether you use the redundant namespace qualifying is up to you.

You might also notice that the template auto-generated some designer code. This code will enable the designer to accept components that you drag onto it. I find this facility most helpful when I drag a database connection to the designer, as it auto-generates the connection string needed to connect to the dragged database.

The first noteworthy bit of generated code is the auto-generated but optional WebServiceBinding attribute. This attribute uses the ConformsTo property to specify which Web Services Interoperability (WSI) specification this Web service claims to conform to and the EmitConformanceClaims property to specify whether this claim is provided when a WSDL of the Web service is published. Personally, I have not done anything with this attribute and since it doesn’t impact what I’m doing with the Web service I just leave it there.

As you might have noticed when you were entering the previous example, the second attribute WebService is not auto-generated. It is optional, though in this case I recommend always adding it. The WebService attribute provides the Web service with two important features:

A guaranteed unique namespace (if used properly). Just like C++/CLI namespaces, this namespace resolves name clashes between multiple Web services.

A description of the Web service for potential consumer clients to read and determine if it is the correct Web service to use.

C H A P T E R 1 5 W E B S E R V I C E S

641

How do you guarantee a unique namespace? It is possible for some third-party developer to create a Web service with the exact same name and members as your Web service. So to stop this from happening, a Web service uses your Web address as a root namespace, because a Web address is guaranteed to be unique for the Web server that hosts the Web service. Of course, it is still required that all Web services be unique on a single Web server.

Here is the code for the WebService attribute from the previous example:

[WebService(Namespace="http://procppcli.net", Description = "Zip code retrieval service")]

Notice that it uses standard attribute syntax.

The declaration of the ref class FindZipCodeClass and its public method GetZip() have nothing particularly special about them, except the attributes WebServiceBinding, WebService, and WebMethod.

Most of Web service magic resides in the last WebMethod attribute. The WebMethod attribute is the only required element (other than the .asmx file) for a Web service. You must add it to any public methods that you want to be accessible within the Web service.

Note Only public members with the [WebMethod] attribute are accessible within the Web service.

Even if the member is public, it will not be accessible unless it has a WebMethod attribute. Just like the WebService attribute, you can include an optional Description of the Method.

[WebMethod(Description = "Get the zip code from city and state")]

The last file generated by the template of current interest is FindZipCodeClass.cpp, shown in Listing 15-3.

Listing 15-3. FindZipCodeClass.cpp

#include "stdafx.h"

#include "FindZipCodeClass.h" #include "Global.asax.h"

namespace FindZipCode

{

int FindZipCodeClass::GetZip(String ^city, String ^state)

{

// Obviously very simplified

if (city->Equals("Louisville") && state->Equals("KY")) return 40241;

else if (city->Equals("San Jose") && state->Equals("CA")) return 95138;

else

throw gcnew Exception("Zip Code not found");

}

};

The public method GetZip() is nothing particularly special, except that it throws an exception on an error. I could have just as easily returned a predetermined value to handle the not found condition, but I want to show you that, when you build consuming clients later in the chapter, exception handling works even over the Internet.

Okay, let’s compile and run the Web service. You can do this the same way as any other application. I use Ctrl-F5, but you can use any method you are comfortable with. What you should get is a Web page that looks something like the one shown in Figure 15-2.

642 C H A P T E R 1 5 W E B S E R V I C E S

Figure 15-2. The FindZipCode Web service Web page

Tip You might get the error “Resource can’t be found.” If you do, check the URL that Visual Studio 2005 is trying to execute. Most likely it is using the solution’s URL instead of the project’s. To fix this, go to Debugging properties of the project and change the HTTP URL to point to the correct place. In my case the URL contains http:// localhost/Chapter15/findzipcode.asmx and this needed to be changed to http://localhost/ FindZipCode/findzipcode.asmx.

Tip You might get the error “Unable to load DLL ‘msvcm80d.dll’.” If you do, it means you compiled your Web service using the /clr:pure option, which has a dependency on this DLL. To get around this error, recompile the project using the /clr:safe option, which doesn’t have this dependency.

I don’t remember coding this Web page, do you? This Web page was automatically created when you compiled your Web service. This page is how a third-party developer will get information about your Web service. Note that I used the term “developer.” The client application will get its information using WSDL. Because I wasn’t very detailed in my descriptions on the WebService and WebMethod attributes, this page isn’t very helpful. I personally recommend that you be as detailed as possible in those attribute descriptions. This will make it easier for a developer to use your Web service.

Go ahead and click the “Service Description” hyperlink to generate and display the WSDL for your Web service. As you can see in Listing 15-4, it’s interesting, but I personally don’t need to know anything about it. I’ll let the computer figure all this out for me.

Listing 15-4. FindZipCode’s WSDL

<?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://procppcli.net"

C H A P T E R 1 5 W E B S E R V I C E S

643

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://procppcli.net"

xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

Zip code retrieval service </wsdl:documentation> <wsdl:types>

<s:schema elementFormDefault="qualified" targetNamespace="http://procppcli.net">

<s:element name="GetZip"> <s:complexType>

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="city" type="s:string" />

<s:element minOccurs="0" maxOccurs="1" name="state" type="s:string" />

</s:sequence>

</s:complexType>

</s:element>

<s:element name="GetZipResponse"> <s:complexType>

<s:sequence>

<s:element minOccurs="1" maxOccurs="1" name="GetZipResult" type="s:int" />

</s:sequence>

</s:complexType>

</s:element>

</s:schema>

</wsdl:types>

<wsdl:message name="GetZipSoapIn">

<wsdl:part name="parameters" element="tns:GetZip" /> </wsdl:message>

<wsdl:message name="GetZipSoapOut">

<wsdl:part name="parameters" element="tns:GetZipResponse" /> </wsdl:message>

<wsdl:portType name="FindZipCodeClassSoap"> <wsdl:operation name="GetZip">

<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> Get the zip code from city and state

</wsdl:documentation>

<wsdl:input message="tns:GetZipSoapIn" /> <wsdl:output message="tns:GetZipSoapOut" />

</wsdl:operation>

</wsdl:portType>

<wsdl:binding name="FindZipCodeClassSoap" type="tns:FindZipCodeClassSoap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />

<wsdl:operation name="GetZip">

<soap:operation soapAction="http://procppcli.net/GetZip" style="document" />

<wsdl:input>

<soap:body use="literal" /> </wsdl:input>

644 C H A P T E R 1 5 W E B S E R V I C E S

<wsdl:output>

<soap:body use="literal" /> </wsdl:output>

</wsdl:operation>

</wsdl:binding>

<wsdl:binding name="FindZipCodeClassSoap12" type="tns:FindZipCodeClassSoap">

<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="GetZip">

<soap12:operation soapAction="http://procppcli.net/GetZip" 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="FindZipCodeClass">

<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> Zip code retrieval service

</wsdl:documentation>

<wsdl:port name="FindZipCodeClassSoap" binding="tns:FindZipCodeClassSoap">

<soap:address location="http://localhost/FindZipCode/FindZipCode.asmx" />

</wsdl:port>

<wsdl:port name="FindZipCodeClassSoap12" binding="tns:FindZipCodeClassSoap12">

<soap12:address location="http://localhost/FindZipCode/FindZipCode.asmx" />

</wsdl:port>

</wsdl:service>

</wsdl:definitions>

Now go back to the previous page and click the GetZip hyperlink. On this page, you get a simple dialog box to test your Web service. I’ll show you the code to do this yourself a little later in this chapter.

Another interesting, but unnecessary, bit of information provided on this page are the HTTP request (see Listing 15-5) and response (see Listing 15-6) SOAP wrappers for your Web service. The reason that I think that they are provided (other than they look cool) is that other platforms are not as lucky as .NET and have to build and parse these SOAP wrappers themselves.

Listing 15-5. FindZipCode’s Request SOAP Wrapper

POST /FindZipCode/FindZipCode.asmx HTTP/1.1

Host: localhost

Content-Type: text/xml; charset=utf-8

Content-Length: length

SOAPAction: "http://procppcli.net/GetZip"

C H A P T E R 1 5 W E B S E R V I C E S

645

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body>

<GetZip xmlns="http://procppcli.net"> <city>string</city> <state>string</state>

</GetZip>

</soap:Body>

</soap:Envelope>

Listing 15-6. FindZipCode’s Response SOAP Wrapper

HTTP/1.1 200 OK

Content-Type: text/xml; charset=utf-8 Content-Length: length

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body>

<GetZipResponse xmlns="http://procppcli.net"> <GetZipResult>int</GetZipResult>

</GetZipResponse>

</soap:Body>

</soap:Envelope>

The last things shown on this page are the request (see Listing 15-7) and response (see Listing 15-8) for an HTTP POST. You’ll probably use this information only in the simplest of Web services and, even then, probably only during the debug phase of that Web service’s development. Other platforms, on the other hand, may need to use this information because they don’t have SOAP support.

Listing 15-7. FindZipCode’s HTTP POST Request

POST /FindZipCode/FindZipCode.asmx/GetZip HTTP/1.1

Host: localhost

Content-Type: application/x-www-form-urlencoded

Content-Length: length

city=string&state=string

Listing 15-8. FindZipCode’s HTTP POST Response

HTTP/1.1 200 OK

Content-Type: text/xml; charset=utf-8

Content-Length: length

<?xml version="1.0" encoding="utf-8"?>

<int xmlns="http://procppcli.net">int</int>

Congratulations, you’ve made your first C++/CLI Web service! Now let’s look at an assortment of ways to access your Web service.

646 C H A P T E R 1 5 W E B S E R V I C E S

Accessing a Web Service Using HTTP POST

Using HTTP POST commands is the easier of the two methods of consuming your Web service. All it requires is some simple HTML code and a Web browser. The problem with using HTTP POST is that the response back from the Web service is an XML document that you will need to parse yourself.

Listing 15-9 shows a sample of some HTML code you might use to consume the Web service. It is basically a stripped-down version of the code generated when you access FindZipCode.asmx.

Listing 15-9. HTML to Consume the FindZipCode Web Service

<HTML>

<BODY>

To execute click the 'Invoke' button.

<form action='http://localhost/FindZipCode/FindZipCode.asmx/GetZip' method="POST">

<table>

<tr>

<td>Parameter</td>

<td>Value</td>

</tr>

<tr>

<td>city:</td>

<td><input type="text" name="city"></td> </tr>

<tr>

<td>state:</td>

<td><input type="text" name="state"></td> </tr>

<tr>

<td colspan="2" align="center">

<input type="submit" value="Invoke"> </td>

</tr>

</table>

</form>

</BODY>

</HTML>

As you can see, there is not much to this HTML. The only tricky parts are as follows:

Use a form action attribute that is made up of the Web service’s name, including the .asmx suffix, followed by the name of the method you want to consume.

Remember to use within your <form> tag a method attribute of POST and not the more common GET.

Make sure the names of the input types match the Web service method parameters’ names.

Figure 15-3 shows the data entry code getzip.html in action. Figure 15-4 shows what the response is after you click the Invoke button.

C H A P T E R 1 5 W E B S E R V I C E S

647

Figure 15-3. Consuming the FindZipCode Web service using getzip.html

Figure 15-4. Response to getzip.html from the FindZipCode Web service

Accessing a Web Service Using SOAP

With .NET, the only real way to consume Web services is to use SOAP. As you saw previously, the SOAP wrapper is quite complex. Fortunately, if you’re using Visual Studio 2005 and C++/CLI (or any other .NET language, for that matter) you don’t have to know squat about SOAP, because pretty well everything about SOAP is taken care of for you. (If you use complex objects you might have to mark up the objects with attributes in order describe how to serialize the object, but that is beyond the scope of this book.)

Normally, when you’re working with distributed programs, the client would be either a Windows Form or a Web Form. In the following example, on the other hand, I use a console to be different and to prove that it can be done. In a later example, I show how to use the more normal Windows Form.

The following example shows how to implement a client using the console. In this example, the client simply requests three zip codes. The first two are valid city/state combinations and the third is an invalid combination. The response to all three requests is written to the console. The third response ends up being a caught exception.

Start by creating a new Console Application (.NET) project. (In the example, I added this project to the chapter solution just to keep all the same code for a chapter together.)