Implementing Service Gateway in .NET
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. Please see the patterns & practices guidance for the most current information. |
Version 1.0.0
GotDotNet community for collaboration on this pattern
Complete List of patterns & practices
Context
You are implementing Service Gateway in an application that will be deployed on the Microsoft Windows operating system. The service to be consumed is a Web service that uses SOAP over HTTP for communications and is fully described by means of Web Services Description Language (WSDL).
Implementation Strategy
Service Gateway recommends encapsulating the code that implements the low-level details of communicating with a service into its own component. The component is responsible for implementing the consumer portion of the communications contract between the consumer and the service provider.
Web services document the service provider portion of the communications contract using WSDL. The wsdl.exe tool in the .NET Framework SDK processes a WSDL description of a Web service and generates a class that encapsulates all the details of communicating with the service through SOAP. For many applications, this proxy class can be used as the service gateway. Some advanced scenarios require the service gateway to have responsibilities beyond basic communication, such as transactions and security; in these scenarios, you may need to wrap the generated class to provide this additional functionality.
Example Overview
This example shows how you could use the implementation strategy described to develop a Web application that displays recording information. The recording catalog service discussed in Implementing Service Interface in .NET provides the recording information.
Figure 1 shows the overall structure of the application.
Figure 1: Application structure
Figure 2 shows the user interface of the example Web application.
Figure 2: Example Web application
This is a very simple Web application. The user enters a recording ID in the Select a Recording text box and clicks Submit. The Web application queries the recording catalog service and displays the result.
Figure 3 shows a detailed class diagram of the application.
Figure 3: Structural view of example Web application
The RecordingCatalog class is the Service Gateway component. It is the class that is generated by the wsdl.exe utility. Further details are discussed later in the pattern.
The RetrievalForm class derives from WebForm class and provides the Web-based user interface for the application.
The Recording and Track classes are defined in the WSDL description of the service and therefore are generated by the wsdl.exe utility. These classes are purely data containers and contain no methods. Their primary role is to act as data transfer objects in interactions with the service.
The RecordingDisplayAdapter and TrackDisplayAdapter classes are wrappers around Recording and Track that adapt their interfaces for ease of use by the user interface controls.
WSDL Description
You need to obtain the WSDL description for the service to be able to generate the service gateway. The WSDL describes the contract that the service gateway and service interface agree on. The service used in this example was developed in ASP.NET. ASP.NET-based Web services have a built-in mechanism for obtaining the WSDL description of the service. In the URL text box of a Web browser, you type the URL of the service with ?WSDL appended to the end.
For example, if the URL of the service is:
https://localhost/ServiceInterface/RecordingCatalog.asmx
you would type the following to display the WSDL description for the service:
https://localhost/ServiceInterface/RecordingCatalog.asmx?WSDL
The WSDL description for the service gateway in the Web application example defines a service named RecordingCatalog as follows. For the implementation in this discussion, only the SOAP port is of interest. This section of the description specifies the URL for the service and references the RecordingCatalogSoap binding.
Note: The full WSDL description is not shown here. However, key excerpts that have an impact on the generated service gateway classes are shown.
<service name="RecordingCatalog">
<port name="RecordingCatalogSoap" binding="s0:RecordingCatalogSoap">
<soap:address location="https://localhost/ServiceInterface/RecordingCatalog.asmx" />
</port>
...
Description of other ports.
...
</service>
The following is the binding definition for RecordingCatalog:
<binding name="RecordingCatalogSoap" type="s0:RecordingCatalogSoap">
</binding>
The next section specifies that the service has one operation, which is named "Get". It also describes the type of message that the service expects and the type of message that the service will respond with:
<portType name="RecordingCatalogSoap">
<operation name="Get">
<input message="s0:GetSoapIn" />
<output message="s0:GetSoapOut" />
</operation>
</portType>
The following section defines the parameters associated with calling the service and the type of what is returned to the user:
<message name="GetSoapIn">
<part name="parameters" element="s0:Get" />
</message>
<message name="GetSoapOut">
<part name="parameters" element="s0:GetResponse" />
</message>
Lastly, the WSDL description specifies, in the form of an XML schema, the Get message to have one element, which is a long value. The response message returns a type, named Recording, which is composed of the recording ID, title, artist, and list of tracks.
<types>
<s:schema elementFormDefault="qualified"targetNamespace="https://msdn.microsoft.com/patterns">
<s:element name="Get">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="id" type="s:long" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetResult" type="s0:Recording" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="Recording">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="id" type="s:long" />
<s:element minOccurs="0" maxOccurs="1" name="title" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="artist" type="s:string" />
<s:element minOccurs="0" maxOccurs="unbounded" name="Track" type="s0:Track" />
</s:sequence>
</s:complexType>
<s:complexType name="Track">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="id" type="s:long" />
<s:element minOccurs="0" maxOccurs="1" name="title" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="duration" type="s:string" />
</s:sequence>
</s:complexType>
<s:element name="Recording" type="s0:Recording" />
</s:schema>
</types>
Service Gateway Implementation
To generate the class and the data transfer classes, you need to execute the following command:
wsdl https://localhost/ServiceInterface/RecordingCatalog.aspx
The name of the output file is based on the service name given in the WSDL description; in this example, the file name is RecordingCatalog.cs. This file contains the source code for three class definitions: RecordingCatalog, Recording, and Track.
For more information on wsdl.exe, see .NET Framework Tools on MSDN:
RecordingCatalog.cs
RecordingCatalog is the class that encapsulates the details of SOAP messaging and HTTP communications. This class exposes a public Get method that is responsible for calling the "Get" operation on the service. Notice the correspondence between the types defined in the XML schema definition and the C# types. The sample application uses this method to obtain the catalog data that will be displayed to the user.
public class RecordingCatalog : System.Web.Services.Protocols.SoapHttpClientProtocol {
/// <remarks/>
public RecordingCatalog() {
this.Url = "https://localhost/ServiceInterface/RecordingCatalog.asmx";
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("https://microsoft.com/pag/patterns/Get", RequestNamespace="https://microsoft.com/pag/patterns", ResponseNamespace="https://microsoft.com/pag/patterns", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public Recording Get(long id) {
object[] results = this.Invoke("Get", new object[] {
id});
return ((Recording)(results[0]));
}
//
}
Recording Data Transfer Object
The following is the implementation of the Recording type specified in the XML schema section of the WSDL description and generated in C# by the wsdl.exe tool:
public class Recording {
/// <remarks/>
public long id;
/// <remarks/>
public string title;
/// <remarks/>
public string artist;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("Track")]
public Track[] Track;
}
Track Data Transfer Object
One of the fields defined in the Recording class refers to the Track type. Track is also specified in the XML schema section of the WSDL file. Its representation as a C# class is defined as follows:
public class Track {
/// <remarks/>
public long id;
/// <remarks/>
public string title;
/// <remarks/>
public string duration;
}
Application Implementation
The remaining three classes, RetrievalForm, RecordingDisplayAdapter, and TrackDisplayAdapter, are used to create a Web page that displays the catalog information.
RetrievalForm.aspx
The RetrievalForm is an ASP.NET page that provides a basic user interface for displaying the data retrieved from the Recording Catalog Web service. It has a text box for entering a recording ID, a button control to initiate the data lookup, and two repeater controls for displaying the returned information. The code for this class is as follows:
<%@ Page language="c#" Codebehind="RetrievalForm.aspx.cs" AutoEventWireup="false" Inherits="RetrieveForm" %>
<HTML>
<HEAD>
<title>Solution</title>
</HEAD>
<body>
<form id="start" method="post" >
<h3>Recordings</h3>
Select a Recording:<br>
<asp:textbox id="TextBox1" ></asp:textbox><asp:button id="Button1" onclick="Button1_Click" text="Submit"></asp:button>
<p><asp:repeater id="RecordingRepeater" >
<HeaderTemplate>
<table cellpadding="3" cellspacing="0" bordercolor="black" border="1" style="background-color:#CCCCFF;border-color:Black;font-family:Verdana;font-size:8pt;width:400px;border-collapse:collapse;">
<tr bgcolor="#aaaadd">
<td>Id</td>
<td>Title</td>
<td>Artist</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# DataBinder.Eval(Container.DataItem, "Id") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Title") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Artist") %></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:repeater></p>
<p>
<asp:Repeater id="TrackRepeater" >
<HeaderTemplate>
<table cellpadding="3" cellspacing="0" bordercolor="black" border="1" style="background-color:#CCCCFF;border-color:Black;font-family:Verdana;font-size:8pt;width:400px;border-collapse:collapse;">
<tr bgcolor="#aaaadd">
<td>Id</td>
<td>Title</td>
<td>Duration</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# DataBinder.Eval(Container.DataItem, "Id") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Title") %></td>
<td><%# DataBinder.Eval(Container.DataItem, "Duration") %></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater></p>
</form>
<P></P>
</body>
</HTML>
RetrievalForm.apsx.cs
RetrevalForm.apsx.cs is the code-behind class for the RetrievalForm page. Its responsibility is to map the results of the call to the Web service to the user interface components. This class uses the RecordingCatalog class to access the Web service. The code is as follows:
using System;
using System.Collections;
using System.Data;
public class RetrievalForm : System.Web.UI.Page
{
private RecordingCatalog catalog = new RecordingCatalog();
protected System.Web.UI.WebControls.Button Button1;
protected System.Web.UI.WebControls.Repeater RecordingRepeater;
protected System.Web.UI.WebControls.Repeater TrackRepeater;
protected System.Web.UI.WebControls.TextBox TextBox1;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
#endregion
protected void Button1_Click(object sender, System.EventArgs e)
{
string stringId = TextBox1.Text;
long id = Convert.ToInt32(stringId);
Recording recording = catalog.Get(id);
if(recording != null)
{
ArrayList recordingAdapters = new ArrayList();
recordingAdapters.Add(new RecordingDisplayAdapter(recording));
RecordingRepeater.DataSource = recordingAdapters;
RecordingRepeater.DataBind();
ArrayList trackAdapters = new ArrayList();
foreach(Track track in recording.Track)
{
trackAdapters.Add(new TrackDisplayAdapter(track));
}
TrackRepeater.DataSource = trackAdapters;
TrackRepeater.DataBind();
}
else
{
RecordingRepeater.DataSource = null;
RecordingRepeater.DataBind();
TrackRepeater.DataSource = null;
TrackRepeater.DataBind();
}
}
}
RecordingDisplayAdapter.cs
The RecordingDisplayAdapter class performs two functions. First, it isolates the user interface from changes made to the types defined in the XML schema section of the WSDL description. Second, because the data binding functionality of the repeater control only works with properties and not public fields, the adapter provides a property interface for the fields defined in the Recording class. The code is as follows:
using System;
public class RecordingDisplayAdapter
{
private Recording recording;
public RecordingDisplayAdapter(Recording recording)
{
this.recording = recording;
}
public long Id
{
get { return recording.id; }
}
public string Artist
{
get { return recording.artist; }
}
public string Title
{
get { return recording.title; }
}
}
TrackDisplayAdapter.cs
The TrackDisplayAdapter class performs the same function for the Track class as the RecordingDisplayAdapter does for the Recording class, as shown in the following code:
using System;
public class TrackDisplayAdapter
{
private Track track;
public TrackDisplayAdapter(Track track)
{
this.track = track;
}
public long Id
{
get { return track.id; }
}
public string Duration
{
get { return track.duration; }
}
public string Title
{
get { return track.title; }
}
}
Tests
The example application consists mostly of generated code, which does not generally require unit tests. The type of testing it requires is referred to as acceptance testing. Acceptance tests exercise the system entirely to ensure that it performs the functions necessary for completion.
Resulting Context
Benefits
Ease of development. Using this implementation approach incurs very little development cost because the tools generate much of the code for you.
Support for both synchronous and asynchronous invocation. The proxy generated by wsdl.exe provides support for both synchronous and asynchronous invocation. Synchronous invocation is simple and easy to work with. Asynchronous invocation can have a very positive impact on performance.
Liabilities
Limited flexibility. You will need to add any functionality the generating class does not provide by developing a custom wrapper class that implements the new functionality and then forwards the request to the class generated by wsdl.exe.
Related Patterns
For more information, see the following related patterns:
Adapter [Gamma95]. Service Gateway is an example of an adapter class. It translates from its own interface into the calls needed to invoke the Web service.
Service Interface. Service Gateway consumes services that are provided by implementations of Service Interface.
Acknowledgments
[Microsoft02-1] Microsoft Corporation. "XML Web Services Overview." .NET Framework Developer's Guide. Available from the MSDN Library at: https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconwebservicesoverview.asp.
[Fowler03] Fowler, Martin. Enterprise Application Architecture Patterns. Addison-Wesley, 2003.
[Gamma95] Gamma, Helm, Johnson, and Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.