Creating an XML Web Service Application

I l @ ve RuBoard

So far, this chapter has focused on describing a Web service and defining the types passed between client and server. However, this is only one consideration when you create a Web service application. Let's look at some of the other issues you'll commonly deal with when you create distributed applications ”such as security, transactions, and exposing existing (heritage) functionality.

Web Services as ASP.NET Applications

Most Web service implementations under .NET will be ASP.NET Web services, which means that they'll have all the underlying capabilities of an ASP.NET Web application. As noted earlier, you'll have access to all of the intrinsic objects available to an ASP.NET Web application, such as Context , Server , and Application . The model of invocation is also the same, in that the page is instantiated to service the request and is then discarded once the method has been completed. This means that many of the same principles apply in terms of component lifecycle.

State Management

The state management model for an ASP.NET Web service is exactly the same as for an ASP.NET Web application ”it uses the Session and Application objects as described in Chapter 16. You might recall that the Session object is created on a per-client basis, normally based on cookies passed between the client and server. The Application object is shared globally between all clients of an application and does not rely on the passing of state information.

As you might expect, access to the Application object does not require any special processing or decoration in an XML Web service. The following code shows how you can maintain a Web service “wide counter by storing it in the Application object:

 /**@attributeWebMethodAttribute()*/ publicintIncrement()throwsSystem.ApplicationException { intcount=0; if(get_Application()==null) { thrownewApplicationException("Applicationwasnull"); } System.Objectobj=get_Application().get_Item("count"); if(obj==null) { get_Application().Add("count",newInteger(0)); } obj=get_Application().get_Item("count"); IntegerobjCount=(Integer)obj; count=objCount.intValue(); count++; objCount=newInteger(count); get_Application().set_Item("count",objCount); returncount; } 

If a client calls this method repeatedly, it will see that the returned count increments .

On the other hand, you do not get a per-client session by default. You must set the EnableSession property of the WebMethod attribute to true for each method that needs to maintain state information. If you want to allow clients to build up an order over multiple calls to the service, you must start by initializing a new order, as shown in the following code:

 /**@attributeWebMethodAttribute(EnableSession=true)*/ publicvoidStartBuiltUpOrder(Stringname,intid) { Orderorder=newOrder(); order.customerName=name; order.orderId=id; get_Session().Add("Order",order); } 

After the customer informs the client application of the items he wants to order, the client can call a method to add each item in turn to the order maintained by the service on the client's behalf :

 /**@attributeWebMethodAttribute(EnableSession=true)*/ publicvoidAddToOrder(Stringdescription,intquantity) throwsSystem.ApplicationException { Itemitem=newItem(); item.itemDescription=description; item.quantity=quantity; Orderorder=(Order)get_Session().get_Item("Order"); if(order==null) { thrownewApplicationException("Orderwasnull"); } intlength=0; Item[]newItems=null; if(order.items==null) { length=1; newItems=newItem[length]; } else { length=order.items.length+1; newItems=newItem[length]; Array.Copy(order.items,newItems,length-1); } //Replacetheitems order.items=newItems; //Addthenewitem order.items[length-1]=item; } 

This approach involves a certain amount of processing overhead. The method first checks to see that there is currently an order for this client; if not, an exception is thrown. The new item is then added to the array of items held in the order. This array manipulation comes about because we're using the same Order object to hold the state as it is passed between client and server for simple orders. Given that this object is accessed only on the server side when you build up the order dynamically, you can, for convenience, rewrite the Order class so that it uses one of the System.Collection types rather than an array. All of the preceding code showing examples of application and session state is contained in the Ordering.asmx.jsl sample file.

One obvious requirement for the session-based state management to work is that the client must support and allow cookies. This support is not enabled by default on client-side XML Web service proxies generated under .NET, so you must enable it explicitly. (This topic is covered in Chapter 18.) XML Web services cannot easily use the cookieless support for sessions. The cookieless support relies on the use of URL rewriting (mangling) and redirection of the client to a mangled URL (an HTTP 302 Found response that tells the client to go to another page). An example is shown here:

 HTTP/1.1302Found Server:Microsoft-IIS/5.1 Date:Tue,07May200212:58:15GMT Location:/CakeCatalogService/(a55ctz55y2oyq0mnsenwv555)/Ordering.asmx Cache-Control:private Content-Type:text/html;charset=utf-8 Content-Length:177 <html><head><title>Objectmoved</title></head><body> <h2>Object movedto <ahref='/CakeCatalogService/(a55ctz55y2oyq0mnsenwv555)/Ordering.asmx'>here </a>.</h2> </body></html> 

A standard Web service proxy generated under the .NET Framework will not handle this response because it expects an HTTP 200 OK response containing a SOAP envelope. You can potentially provide your own proxies or roll your own sessions by passing a customer SOAP header, but a discussion of such strategies is beyond the scope of this chapter.

Signaling Errors

When you create classes and interfaces for use under the .NET Framework, you generally signal error conditions using exceptions. You can subclass the ApplicationException class and define your own application-specific exceptions. But what happens when you need to indicate an error across a Web service interface?

Consider the scenario in which a client calls the Web method GetCustomer . If the code used by the method cannot access the database it needs, an SqlException will be generated. Unless you catch the exception, it will ripple all the way down the stack until it reaches the point at which the GetCustomer call arrived at the server. This exception must somehow be propagated back to the client. However, SOAP does not support the concept of an exception. Instead, a SOAP response can include a SOAP Fault to indicate that an error occurred on the server.

The format of a .NET exception includes a stack trace and possibly nested exceptions. This does not map easily into a SOAP Fault, so the .NET Framework Class Library defines the System.Web.Services.Protocols.SoapException class, which contains a set of properties that map to a SOAP Fault, including

  • The message detailing the error.

  • A fault code indicating an error with the call (not an internal application error). The SoapException class contains some fixed values you can use for this, such as SoapException.ClientFaultCode .

  • The name of the Web service method.

  • Additional error details supplied as XML in an XmlNode object.

In the case of an unhandled exception, the message is taken from the original exception, such as the SqlException in the example, and copied to the generated SoapException . The name of the originating Web method is set on the SoapException , and the SoapException is then converted into a SOAP Fault and sent back to the client.

The benefit of encapsulating error information in a SOAP Fault is that any type of client can take this information and interpret it. In the case of a client built using the .NET Framework, the SoapException is regenerated based on the SOAP Fault. All of the usual .NET Framework exception information, such as the stack trace, is added at this point. The exception is then thrown on the client side and hopefully caught by the client code!

The default error handling provided in a .NET Framework XML Web service is a useful fallback for propagating errors. However, it is not ideal because the message from the underlying exception might not have much meaning to the client. Therefore, it is best to catch exceptions that occur during a Web method call and then explicitly generate SoapExceptions containing the appropriate information. The advantage of this approach is that not only do you get to set a meaningful message, but you can also include more information using the Detail property.

To use the Detail property, you must create an appropriate XML document that can be used as part of the SOAP Fault. This document must contain a predefined root element named detail to fit with the SOAP specification. The correct name and namespace are provided by the read-only DetailElementName and DetailElementNamespace properties of the SoapException class. The following code, taken from the EnquiryWithException.asmx.jsl sample file, shows how to use these properties:

 XmlDocumentdoc=newXmlDocument(); //CreateaSOAP-compliantdetailelementthatcanbe //usedaspartoftheSOAPFault. XmlElementroot= doc.CreateElement(SoapException.DetailElementName.get_Name(), SoapException.DetailElementName.get_Namespace()); doc.AppendChild(root); 

You can then populate the detail element with your own error information. The following example shows a version of the FeedsHowMany method that throws a SoapException if either of its string parameters is null :

 public intFeedsHowMany(intdiameter, Stringshape, Stringfilling)throwsSoapException { if(filling==nullshape==null) { StringdetailNamespace= "http://fourthcoffee.com"; Stringprefix= "coffee"; XmlDocumentdoc=newXmlDocument(); //CreateaSOAP-compliantdetailelementthatcanbe //usedaspartoftheSOAPFault. XmlElementroot= doc.CreateElement(SoapException.DetailElementName.get_Name(), SoapException.DetailElementName.get_Namespace()); doc.AppendChild(root); //Addourowncontenttothedetailmessage XmlElementparams=doc.CreateElement(prefix, "Params",detailNamespace); root.AppendChild(params); XmlElementxmlShape=doc.CreateElement(prefix, "Shape", detailNamespace); xmlShape.AppendChild(doc.CreateTextNode(shape)); params.AppendChild(xmlShape); XmlElementxmlFilling=doc.CreateElement(prefix, "Filling", detailNamespace); xmlFilling.AppendChild(doc.CreateTextNode(filling)); params.AppendChild(xmlFilling); thrownewSoapException("Youpassedinanullargument", SoapException.ClientFaultCode, "FeedsHowManyWithException", doc); } } 

A Params element is created below the details element. This Params element contains two other elements, each of which represents the value of one of the two string parameters passed into the method. By examining this information, the client can determine which parameter was incorrect. These child elements are defined in a namespace specific to the example company (Fourth Coffee); this is good practice whenever you create documents to be passed across Web service boundaries.

The SoapException is created using the generated detail document. The use of the ClientFaultCode flag indicates that the processing difficulty was due to a problem with the parameters sent from the client. Note that the method must declare that it throws a SoapException . The SoapException can be caught on the client in a try/catch block. The SOAP Fault generated by passing a null Shape argument is shown here:

 HTTP/1.1500InternalServerError. Server:Microsoft-IIS/5.1 Date:Tue,07May200213:40:32GMT Cache-Control:private Content-Type:text/xml;charset=utf-8 Content-Length:816 <?xmlversion="1.0" encoding="utf-8"?> <soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>soap:Client</faultcode> <faultstring>System.Web.Services.Protocols.SoapException:Youpassed inanullargumentat CakeCatalogService.EnquiryWithException.FeedsHowMany(Int32diameter, Stringshape,Stringfilling)in c:\inetpub\wwwroot\CakeCatalogService\EnquiryWithException.asmx.jsl:line 86</faultstring> <faultactor>FeedsHowManyWithException</faultactor> <detail> <coffee:Paramsxmlns:coffee="http://fourthcoffee.com"> <coffee:Shape> </coffee:Shape> <coffee:Filling>fruit</coffee:Filling> </coffee:Params> </detail> </soap:Fault> </soap:Body> </soap:Envelope> 
Securing a Web Service

XML Web services based on ASP.NET use the same security mechanisms as ASP.NET Web applications (discussed in Chapter 16). Given the nature of Web services, some security options such as forms-based authentication are redundant. However, new options are also available, such as the use of SOAP headers to carry authentication information. You could write a whole chapter (if not a whole book) on Web services and security, so this section will employ the Pareto Principle ( otherwise known as the 80/20 rule) and concentrate on the three main options:

  • Windows-based authentication

  • Windows-based authentication over SSL

  • Certificate-based authentication (which includes SSL by default)

The type of security you use will depend on your requirements for authentication and privacy. If you need privacy for data in transit, you must select one of the SSL options. If you do not need privacy, it is quicker and easier to use Windows-based authentication. Be sure that you have Windows authentication specified in the Web.Config file for your Web service:

 <configuration> <system.web> <authenticationmode="Windows" /> </system.web> </configuration> 

In the context of the online cake store, you might want to provide access to certain additional services that are restricted to business partners. For example, consider an XML Web service called PartnerServices.asmx that contains the following method to give partners access to the secret Fourth Coffee cake recipe (which is not fully listed here due to commercial confidentiality):

 /**@attributeWebMethodAttribute()*/ publicStringGetRecipe() { return "Eyeofnewtandtoeofbat..."; } 

By default, Web services built using Visual Studio .NET are configured to allow anonymous access. This means that the client does not need to take any special measures to propagate their security information. Such unlimited access might be fine in an intranet environment, but it's not suitable for interorganization or cross-department Web services in which security is important. The need for authentication will also vary depending on the sensitivity of the functionality being exposed. (You would not want to allow everyone to increase their salary by using the HR Web service, for example!).

To protect a Web service, you can use IIS security to indicate the type of authentication required, as follows :

  • From the Start menu, choose Control Panel and then select Performance And Maintenance.

  • Select Administrative Tools and double-click on Internet Information Services.

  • Open the entry for your computer (or the server on which you deploy your Web services), and then open the Web Sites folder.

  • Locate the virtual directory containing your Web service file and select the Web services file to be secured (in this case, the PartnerServices.asmx sample file under the CakeCatalogService folder), as shown in Figure 17-8.

    Figure 17-8. You can set security permissions down to the individual Web-service level.

  • Choose Properties from the shortcut menu for this file to display the PartnerServices.asmx Properties dialog box.

  • Click on the File Security tab and click the Edit button. The resulting Authentication Methods dialog box is shown in Figure 17-9.

  • Clear Anonymous Access and select the type of Windows authentication required.

    Figure 17-9. Selecting an appropriate form of Windows authentication for your Web service

If a client now tries to access the service without authentication, it will receive this error message:

 TherequestfailedwithHTTPstatus401:Accessdenied. 

To access the Web service, the client must provide credentials to authenticate itself. These credentials can be a combination of username, password, and domain name. If the client is part of the same domain or workgroup as the Web service host computer, you can also use integrated security. Details on passing credentials from a client to a .NET Framework Web service are provided in Chapter 18.

You can grant or deny access to a specific Web service for a particular user or set of users by using the <authorization> element in the Web.Config file. This is the same mechanism employed by APS.NET Web applications. Chapter 16 discussed the use of the <authorization> element in detail.

You've now added authentication to your Web service. To protect data in transit you can employ SSL. To use SSL, you must obtain and install a server certificate. Clients will then access your Web service through a URL starting with https :// in place of http:// . You can make this happen by editing the WSDL generated by the Web service to use https in its service URL or by changing the URL used by the client at run time. Details about setting the target URL for a Web service client at run time are discussed in Chapter 18.

For a more flexible security solution across organizational boundaries, you can use client certificates. As before, you should use Windows authentication, but you should also associate specific client certificates with identities defined on your server. This configuration is performed under the Internet Information Services administration tool used earlier by using the Properties dialog box of the specific Web service files or over the whole virtual directory containing your Web services. On the File Security or Directory Security tab of the Properties dialog box, you'll find a section labeled Secure Communications that allows you to configure client certificates. For more details, see the product documentation. Once you've configured the certificates to use, you can pass them as part of the SOAP message sent by the client. Again, for more details, see Chapter 18.

You can find details on using SOAP Headers as a means of transporting authentication information in the .NET Framework documentation in the section titled "Securing XML Web Services Created Using ASP.NET."

Transactions and Web Services

Many business applications require updating of multiple databases as part of one business operation. To do this in a controlled and consistent way, you generally need to employ transactions. You can write code that controls transactions on a per-connection basis, or you can interact with the Distributed Transaction Coordinator (DTC) to take part in transactions that span multiple databases. As you saw in Chapter 14, serviced components , such as those provided by COM+, can take part in transactions without your having to write transaction-specific code. The component is tagged with TransactionAttribute , indicating to its container the type of transaction support it requires. Even if a transaction does not span multiple databases, you might find it convenient to use declarative transactions to simplify development.

Just as transactions are important to components, they are also important to Web services. A Web service will typically occupy the same role in an application as a serviced component ”as a repository of business logic to be called by a client as necessary. Hence, you want to obtain the same level of transactional control. As before, you can control transactions on a per-connection basis if you're communicating with a single database. Methods for controlling such transactions are provided through the ADO.NET connection classes. However, you can also use declarative transactions with ASP.NET-based Web services by taking advantage of the transaction support in the System.EnterpriseServices namespace. To do this, you must specify the TransactionOption property of the WebMethod attribute. You assign this property a value from the TransactionOption enumeration to indicate what type of transaction support you need for the method. (The TransactionOption enumeration lives in the System.EnterpriseServices namespace, so remember to import this into the Web service JSL file and to add the System.EnterpriseServices.dll assembly as a reference into the project.)

The following code shows an updated form of the SubmitOrder method that uses a transaction attribute:

 /**@attributeWebMethodAttribute(TransactionOption= TransactionOption.RequiresNew)*/ publicWsReceiptSubmitOrder(Orderorder, booleanshouldFail) { //Shouldprobablyhavesomedatabasestuffhere... if(shouldFail) { thrownewSoapException("Becauseyouaskedforthis..." + msg,SoapException.ClientFaultCode); } WsReceiptreceipt=newWsReceipt(order.get_CustomerName(), order.get_OrderId(),DateTime.get_Now()); returnreceipt; } 

When a client calls this form of the SubmitOrder method, a new transaction will be started in which the method will run. An exception will cause the transaction to roll back. If no exceptions are thrown, ASP.NET will try to commit the transaction. You can access the current transaction context through the ContextUtil class and use the static methods SetAbort and SetComplete to control the outcome of the transaction.

It is important to understand that a Web method can act only as the root of a transaction. There is currently no standard way of propagating transaction information over SOAP calls (at least not until the GXA arrives), so a transaction started in a Web service client will be suspended while the Web service call is made. If a transaction is started by ASP.NET in response to a Web service method call, the transaction will propagate through calls to other .NET objects and COM+ components. Any updates to transactional resources, such as a database, made by these objects or components will become part of the transaction. These objects or components can affect the outcome of the transaction by throwing exceptions or setting the transaction state in the transaction context (through such methods as SetAbort ).

In the same way that a client cannot propagate a transaction to a Web service, one Web service cannot propagate its transaction onto another Web service ”the same issue of crossing a SOAP boundary applies. If a Web service method calls another Web service method that is denoted as transactional, it will suspend any existing transaction and start a new transaction for the new method.

Exposing Existing Applications as Web Services

In some cases, you'll create Web service-based applications from scratch. But in other cases, you might want to incorporate existing functionality into new Web service applications or provide Web service wrappers for existing functionality. We'll discuss this topic next .

What Do You Have Already?

Existing functionality can come in a variety of forms:

  • If you've been pursuing a component-based strategy, much of your business logic might already be encapsulated in COM or COM+ components. In this case, you can use these components from ASP.NET managed code as described in Chapter 13 and Chapter 14.

  • The code might exist as discrete methods in an existing DLL. In this case, you can access this functionality through the P/Invoke mechanism discussed in Chapter 13.

  • You might have business code encapsulated in a Web-oriented application, such as ASP pages. In this case, you must factor out the existing code into methods that can be used as part of an ASP.NET-based Web service or a supporting class.

  • The existing functionality might belong to a GUI-based application. As with the Web-oriented application, if the business logic is embedded in the GUI code, you must separate it out into discrete methods.

When you're looking to reuse existing functionality as part of a Web service, you should take some time to think about whether it actually fits into the Web service model. Considerations include:

  • If the functionality was not previously distributed, will the functionality work with multiple users and with the anticipated number of users of the Web service?

  • Are specific data types being passed around that will be difficult to expose through a Web service?

  • Is the granularity appropriate for a Web service? If the functionality needs the client to make many method calls to achieve its aims, a lot of overhead and network traffic will result.

If you anticipate serious issues with the functionality you want to expose, you might want to use an alternative strategy, as described next.

Strategies for Exposing Functionality

To expose existing functionality as a Web service, you can choose from a few different approaches, depending on your requirements:

  • If the functionality already exists as a DLL or COM/COM+ component, you might want to simply create a Web service server-side proxy for it. This involves creating a Web service containing a set of methods that match those of the DLL or component; the Web service simply delegates the calls to the DLL or component.

  • If you have issues with data types or granularity, you can design a Web service-oriented interface for the functionality. You can then create a Web service facade based on this interface that will call the existing functionality as needed. This will also allow you to reduce network traffic or make interaction easier compared to using a simple server-side proxy.

  • If you're not sure about the best distribution strategy to use, you can wrap the functionality as ordinary .NET classes that are deployed in regular assemblies. These classes can then be called from ASP.NET-based Web services or from .NET Remoting remote objects. If you're exposing functionality through .NET Remoting, you can use this approach to deliver the functionality across raw TCP/IP or over HTTP channels. If you're using a .NET Remoting server that's configured to use the HTTP transport and XML serialization, a client can access the server as a Web service using the SoapSuds utility, as discussed in Chapter 18.

Ultimately, Web services are a very flexible form of distribution. But as with any form of distribution, you must be careful about what you distribute and consider the consequences of doing so.'

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net