Tải bản đầy đủ
Example: Building an ASP.NET Web Service to Access the Mainframe Gateway

Example: Building an ASP.NET Web Service to Access the Mainframe Gateway

Tải bản đầy đủ

Chapter 4: System Connections 165

Service Consumer

Service Contract

Service Provider

Client Implementation
Class MyClient
{
Service s =
new Service();
s.Do(...)
}

Service Activator

Message.xsd

...


6

xsd.exe

Class Message
{...}

2

[WebMethod]
Do(Message msg)
{...}

3

1
Service Implementation

Service Gateway
Interface lBackEnd
{
void GoDo(Parms parms)
}

Class Message
{...}
Class Service
{
Do(Message msg)
}

4
BackEndStub

BackEndlmpl

Go Do(Parms)

Go Do(Parms)

BackEndFactory
lBackEnd GetBackEnd()
NET DISCO

Design Time
RunTime

WSDL


...

Figure 4.21
Building an ASP.NET Web service

.NET / llS
5

166 Integration Patterns

Step 1: Develop XSD Documents
The first step is to define the format of the request and response messages by creating two XSD files. The following are the XSD files for the GetAccountInfo method
generated using the Microsoft Visual Studio .NET XSD Editor. To ensure maximum
interoperability between the gateway service and the consumers, this implementation uses only standard data types and avoids using .NET Framework – specific data
types such as DataSet.
The XML schema for the request message (GetAccountInfoRequest.xsd) looks like
the following example. (To improve readability, the namespace declarations have
been omitted.)










You will notice that the document for the request is quite simple; it contains only a
single string data element named .
The following sample is what the XML schema response
(GetAccountInfoResponse.xsd) looks like.













Chapter 4: System Connections 167

After creating the XSD files, the next step is to generate the data transfer classes.

Step 2: Generate Data Transfer Classes
The XML Schema Definition tool (xsd.exe) in the .NET Framework can create a .NET
Framework class from an XSD file. To create the data transfer files, run the following
commands from the command prompt in the directory where the XSD files reside:
xsd /n:GatewayWS /c GetAccountInfoRequest.xsd
xsd /n:GatewayWS /c GetAccountInfoResponse.xsd

These commands generate the GetAccountInfoRequest.cs and
GetAccountInfoResponse.cs files. Using the /namespace option (or the short form,
/n) enables you to specify the namespace to be used for the generated class. Otherwise, the global namespace is used as the default namespace, which could lead to
conflicting namespaces.
The generated class file GetAccountInfoRequest.cs is shown in the following sample.
namespace GatewayWS {
using System.Xml.Serialization;

public class GetAccountInfoRequest {
[System.Xml.Serialization.XmlElementAttribute
(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string AccountID;
}
}

The attribute in front of the AccountID field instructs the XML Serializer that no
namespace qualifier is required in the XML string for this element.
The generated class has no functionality, but rather it is a simple data holder class
that acts as a Data Transfer Object [Trowbridge03] between the .NET Framework and
the service implementation. This class is the .NET Framework representation of the
XML document that is embedded in a SOAP request to the gateway service. As
described previously, at run time the XML Serializer parses the incoming SOAP
document, creates an instance of this object, and populates all fields with the values
from the incoming SOAP XML document.

168 Integration Patterns

Step 3: Define the Operations That the Service Exposes
Now that you have data transfer objects, you can define the methods and operations
that the service is going to expose. As described in “ASP.NET Web Services” earlier
in this pattern, an ASP.NET Web service endpoint is created from the combination of
an .asmx file and an associated code-behind page that contains the actual class
definition. Because the Visual Studio .NET tool set generates the .asmx file for you,
you can focus on the Gateway class itself, which is contained in the file
Gateway.asmx.cs. This implementation follows the Service Interface [Trowbridge03]
approach and separates the public service interface from the implementation.
The class Gateway inherits from the base class WebService. To keep things simple,
the class exposes only a single method, GetAccountInfo, as a service.
namespace GatewayWS
{
[WebService(Namespace="http://msdn.microsoft.com/patterns/")]
public class Gateway : System.Web.Services.WebService
{

[WebMethod]
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
public GetAccountInfoResponse GetAccountInfo(
GetAccountInfoRequest GetAccountInfoRequest)
{
return null;
}
}
}

For now, leave the method body empty and only return a null value. You will tie this
method to the service implementation in the next step.
Note that both the method and the class are encoded with special attributes. The
[WebMethod] attribute of the GetAccountInfo method makes the method accessible
as part of the Web service. The additional [SoapDocumentMethod(…)] attribute
customizes the way the XML Serializer parses incoming SOAP messages. By default,
the XML Serializer expects method parameters to be wrapped inside an additional
element, which is in turn contained in the element. Changing the
ParameterStyle setting to SoapParameterStyle.Bare makes these method parameters appear immediately under the element, rather than encapsulated
in an additional XML element.

Chapter 4: System Connections 169

The following example shows a SOAP message that causes the GetAccountInfo
method of the Gateway.asmx Web service to be invoked.

xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">


1234567




The element contains a element. This
element corresponds to the single parameter that the GetAccountInfo method
receives.
Without the ParameterStyle setting, the SOAP request for the same method would
look like the following sample. Note that an additional GetAccountInfo node
beneath the element wraps the method parameters.

xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">



1234567





Because the method receives all the necessary data elements inside a single data
transfer object, the additional wrapping is not required and makes the SOAP message unnecessarily verbose. Therefore, set the ParameterStyle to Bare.

Step 4: Connect the Service Interface to the Service Implementation
Now that you have built the service interface and have encoded it with the necessary Web service attributes, you need to link the still-empty GetAccountInfo
method to the actual service implementation. One option is to insert the code that
implements the service into the GetAccountInfo method of the Gateway class.
However, this approach has a number of drawbacks.

170 Integration Patterns

First, the Gateway class inherits from the WebService base class. That means that
the class cannot be part of a separate inheritance tree that the service implementation may require.
Second, tying together the Web service interface and the implementation makes it
harder to test the implementation outside the Web services context.
Third, the functions that the service implementation provides may not exactly match
the service interface definition. For example, the service implementation may require multiple calls to fine-grained methods, whereas the Web service interface
should expose coarse-grained functions. Or, the service implementation may use
.NET Framework – specific data types, such as DataSets, that you are seeking to
avoid in the public Web service interface. As a result, the service may need to contain logic to arbitrate between the public Web service interface and the internal
implementation.
Finally, tying the Web service directly to the implementation means that the Web
service can be functional only when the service implementation is running and is
available. That may not always be the case if the actual service implementation
resides in an existing system. For example, many mainframe systems have offline
times when they are not available for online requests. As the Web service is weaved
into a larger solution, these outages could hinder testing. For testing purposes, it
would be very convenient to be able to replace the service implementation with a
dummy implementation without affecting the service interface.
All these problems can be solved with a combination of well-known design patterns
that is shown in Figure 4.22. The first step is to separate the interface of the service
functionality from the implementation — for example, the mainframe access. You can
do so by applying the Separated Interface pattern [Fowler03]. The interface
IGlobalBank is a generic interface that represents the functions provided by the
mainframe system, but it has no dependency on the server running HIS. The class
GlobalHIS implements the methods specified in this interface by connecting to the
mainframe through HIS.

Chapter 4: System Connections 171

Gateway

IGlobalBank

GlobalStub

<>

GlobalHIS

HIS

<>

GlobalPlugin
Factory
Figure 4.22
Separating the service implementation from the service interface

After you have separated the interface from the implementation, you can create a
Service Stub [Fowler03]. A service stub is a dummy implementation of an external
service that reduces external dependencies during testing. The GlobalStub service
stub implements the same IGlobalBank interface but does not actually connect to
the mainframe computer. Instead, it simulates the mainframe functions internally.
Now that you have two implementations, you have to decide which one to use. You
want to be able to switch between the dummy implementation and the real implementation without having to change any code or having to recompile. Therefore,
this example uses a Plugin [Fowler03]. Plugin links classes during configuration
rather than compilation. You implement the Plugin inside the GlobalPlugInFactory
class. The factory class reads the name of the implementing class from a configuration file so that you can switch between GlobalStub and GlobalHIS at run time by
changing the application configuration file.

172 Integration Patterns

What is left for the Gateway class to do? It has to call the GlobalPlugInFactory class
to obtain a reference to an implementation of the IGlobalBank interface. Next, it has
to invoke the appropriate method in the interface. The names and types of the
parameters of the service implementation may differ from the XML schemas that
you created, so the Gateway class may have to perform simple mapping functions
between the two data representations.
Even though the implementation of these extra classes is not strictly necessary to
create a working Web service, these patterns simplify testing tremendously and are
well worth the additional coding effort. It turns out that the implementation of each
class is actually quite simple. The implementation involves the following steps:
1. Create an interface.
2. Create service implementations.
3. Create the plug-in factory.
4. Implement the Web service method.
Let’s walk through these steps one by one.
Step 4.1: Create an Interface

First, create an interface for the service implementation. The following code is from
the IGlobalBank.cs file. The code references the AccountInfo class. The AccountInfo
class is used by the service implementation. Note that this interface and the data
transfer class have no dependency on a specific service
implementation.
public interface IGlobalBank
{
// Throws ArgumentException if account does not exist.
AccountInfo GetAccountInfo (string AccountID);
}
public class AccountInfo
{
public string accountID;
public string name;
public string description;
public decimal balance;
public AccountInfo(string accountID, string name,
string description, decimal balance)
{
this.accountID = accountID;
this.name = name;
this.description = description;
this.balance = balance;
}
}

Chapter 4: System Connections 173

Step 4.2: Create the Service Implementations

Next, create two implementations of the interface as shown. Create one that is a
simple stub, and create another one that connects to the mainframe system through
HIS.
The two implementation classes are named GlobalHIS and GlobalStub. GlobalHIS
connects to the external system. In the example, the mainframe gateway is the
external system. The class implements the IGlobalBank interface.
public class GlobalHIS : IGlobalBank
{

public AccountInfo GetAccountInfo(string accountID)
{
decimal balance = 0.00m;
string name = "";
object [] contextArray = null;
TCP_LinkTRM_NET.GlobalBank bank = new TCP_LinkTRM_NET.GlobalBank ();
bank.cedrbank (ref name ,ref accountID ,ref balance,
ref contextArray);
AccountInfo info = new AccountInfo(accountID, "","", balance);
return info;
}
}

The GlobalStub class provides another implementation of the IGlobalBank interface but is a simple stub without any dependency on external systems. It uses an
internal accounts collection to simulate account balances. For testing purposes, the
class constructor initializes this collection by using hard-coded values.
public class GlobalStub : IGlobalBank
{
static IDictionary accounts = (IDictionary) new Hashtable();
public GlobalStub()
{
if (accounts.Count == 0)
{
accounts.Add("123",
new AccountInfo("123", "TestAccount", "TestDescription", 777.12m));
}
}
public AccountInfo GetAccountInfo(string accountID)
{
if (!accounts.Contains(accountID))
throw new ArgumentException("Account does not exist");
return (AccountInfo)accounts[accountID];
}
}

174 Integration Patterns
Step 4.3: Create the Plug-in Factory

Now that you have created two implementations of the IGlobalBank interface, you
need to decide the implementation that you want to use at run time. This is the
purpose of the GlobalBankPlugInFactory class. The class reads the name of the
class to be used from the configuration file and then creates an instance of that class.
It returns a reference to the newly created object to the service interface, but the
reference is cast to the IGlobalStub interface. This way the service interface is not
dependent on the implementation that was chosen.
public class GlobalBankPlugInFactory
{
static string globalBankImplName =
System.Configuration.ConfigurationSettings.AppSettings["GlobalBankImpl"];
static public GlobalBank.IGlobalBank GetGlobalBankImpl()
{
Type globalBankImplType = Type.GetType(GetImplementationClassName());
if (globalBankImplType == null)
throw new TypeLoadException("Cannot load type " + globalBankImplName);
GlobalBank.IGlobalBank bank =
(GlobalBank.IGlobalBank)Activator.CreateInstance(globalBankImplType);
return bank;
}
static public string GetImplementationClassName()
{
if (globalBankImplName == null)
globalBankImplName = "GlobalBank.GlobalStub";
return globalBankImplName;
}
}

For the Plugin class to be functional, you need to add the following entry to the
Web.config file.




Chapter 4: System Connections 175

Step 4.4: Implement the Web Service Method

Now that you have defined the implementation classes, you can finally fill in the
implementation code to the GetAccountInfo method of the Gateway.asmx Web
service as follows.
[WebMethod]
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Bare)]
public GetAccountInfoResponse GetAccountInfo(
GetAccountInfoRequest GetAccountInfoRequest)
{
GlobalBank.IGlobalBank bank =
GlobalBank.GlobalBankPlugInFactory.GetGlobalBankImpl();
GlobalBank.AccountInfo globalAccountInfo =
bank.GetAccountInfo(GetAccountInfoRequest.AccountID);
return BuildAccountInfo(globalAccountInfo);
}
private GetAccountInfoResponse BuildAccountInfo(GlobalBank.AccountInfo
globalAccountInfo)
{
GetAccountInfoResponse response = new GetAccountInfoResponse();
response.AccountID = globalAccountInfo.accountID;
response.Balance = globalAccountInfo.balance;
response.Name = globalAccountInfo.name;
response.Description = globalAccountInfo.description;
return response;
}

The preparation you have done pays off. The implementation of the Web service
interface method now consists of three easy steps:
1. Obtain a reference to the IGlobalBank interface.
2. Invoke the service implementation by using the reference.
3. Construct the correct response format to return to the consumer.
Each step can be implemented in a single line of code. Step 3 is implemented in a
BuildAccountInfoResponse private helper method. In this contrived example, it
might appear unnecessary to use separate structures for GetAccountInfoResponse
and GlobalBank.AccountInfo because they are essentially identical. However, each
structure is likely to undergo a different change cycle over time. Including this
translation step allows the gateway to accommodate changes to either the HIS
interface or to the gateway interface without affecting both interfaces.