Ultimately, the combination of logic-to-contract and
consumer-to-contract is considered to establish the healthiest
foundation for achieving balanced loose coupling within and between
services. The most effective way of realizing this is by establishing a
service contract that is physically independent of both service
consumers and its underlying implementation (Figure 1). This is the goal of the Decoupled Contract pattern.
By positioning the service
contract as a standalone artifact, we force the service implementation
to adhere to the contract and we allow for the Contract Centralization pattern to be applied in such a way that service consumers cannot become dependent on the service implementation.
When building services with .NET, there are several ways of accomplishing this, as explored in these next sections.
WSDL-First
Based on the data types we will create message types that are
essentially XML schemas tailored for use in specific messages.
Thereafter, we will create a WSDL definition that imports these schemas.
Our service will have three operations:
GetPerson
GetAllPersons
UpdatePerson
We need to import that type into a new XML schema, as follows:
Example 1.
<xs:import namespace= "http://schemas.example.org/enterprise/models/v1" schemaLocation="Personperson.xsd"/>
|
We also need to create a namespace that matches the namespace of the imported XML schema:
xmlns:persondata="http://schemas.example.org/
enterprise/models/v1"
Lastly, when we want to use the person type in an element we declare its type like this:
The completed message schema that we place in the PersonServiceMessages.xsd file looks like this:
Example 2.
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:persondata= "http://schemas.example.org/enterprise/models/v1" xmlns:tns= "http://schemas.example.org/enterprise/service/v1" targetNamespace= "http://schemas.example.org/enterprise/service/v1"> <xs:importnamespace= "http://schemas.example.org/enterprise/models/v1" schemaLocation="Person.xsd"/> <xs:element name="GetPerson"> <xs:complexType> <xs:sequence> <xs:element name="personId" type="xs:positiveInteger" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetPersonResponse"> <xs:complexType> <xs:sequence> <xs:element name="person" type="persondata:person" minOccurs="0" nillable="true"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetAllPersons"> <xs:complexType> <xs:sequence/> </xs:complexType> </xs:element> <xs:element name="GetAllPersonsResponse"> <xs:complexType> <xs:sequence> <xs:element name="person" type="persondata:personList" minOccurs="0" nillable="true"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="UpdatePerson"> <xs:complexType> <xs:sequence> <xs:element name="updatablePerson" type="persondata:person" minOccurs="0" nillable="true" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="UpdatePersonResponse"> <xs:complexType> <xs:sequence> <xs:element name="success" type="xs:boolean" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
|
After creating the message types, they are imported into the WSDL definition. Using the import statement we showed earlier, the WSDL definition is structured as follows:
Example 3.
<definitions xmlns:tns= "http://schemas.example.org/enterprise/service/v1" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:personMessages= "http://schemas.example.org/enterprise/service/v1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="PersonService" targetNamespace= "http://schemas.example.org/enterprise/service/v1" xmlns="http://schemas.xmlsoap.org/wsdl/"> <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/> <types> <xsd:schema> <xsd:import schemaLocation="PersonServiceMessages.xsd" /> </xsd:schema> </types> <message name="getPersonIn"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element="personMessages:GetPerson" /> </message> <message name="getPersonOut"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element="personMessages:GetPersonResponse" /> </message> <message name="getAllPersonsIn"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element="personMessages:GetAllPersons" /> </message> <message name="getAllPersonsOut"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element="personMessages:GetAllPersonsResponse" /> </message> <message name="updatePersonIn"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element="personMessages:UpdatePerson" /> </message> <message name="updatePersonOut"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <part name="parameters" element= "personMessages:UpdatePersonResponse" /> </message> <portType name="PersonServiceInterface"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <operation name="GetPerson"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <input message="tns:getPersonIn" /> <output message="tns:getPersonOut" /> </operation> <operation name="GetAllPersons"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <input message="tns:getAllPersonsIn" /> <output message="tns:getAllPersonsOut" /> </operation> <operation name="UpdatePerson"> <wsdl:documentation xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/" /> <input message="tns:updatePersonIn" /> <output message="tns:updatePersonOut" /> </operation> </portType> <binding name= "BasicHttpBinding_PersonServiceInterface "type="tns:PersonServiceInterface"> <soap:binding transport= "http://schemas.xmlsoap.org/soap/http" /> <operation name="GetPerson"> <soap:operation soapAction= "http://schemas.example.org/enterprise/service/v1: getPersonIn" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> <operation name="GetAllPersons"> <soap:operation soapAction= "http://schemas.example.org/enterprise/service/v1: getAllPersonsIn" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> <operation name="UpdatePerson"> <soap:operation soapAction= "http://schemas.example.org/enterprise/service/v1: updatePersonIn" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> </definitions>
|
To summarize, the WSDL definition we created includes:
an import of the message schemas
definitions of messages (for example, “getPersonIn”) that refer to the imported message schemas
the service interface that specifies operations and refers to the declared messages
the binding that specifies SOAP operations for the previously specified operations
Now that we have a Web service
contract and associated data model designed via the WSDL-first approach,
we need to make it part of our development environment so that we can
build the corresponding implementation logic.
There are several tools that can be used for this purpose. The three tools we highlight in the upcoming sections are:
svcutil
WSCF.blue
WSCF.classic
Be sure to study the pros and
cons of each tool before choosing which to make part of a standard
service-oriented design process for Web services.
Generating Service Code Using Svcutil
The svcutil utility can be used
to generate code based on an existing WSDL definition. To launch this
utility, we need to type the following statement in the Visual Studio
command prompt:
Example 4.
svcutil -serializer:datacontractserializer -namespace:*, schemas.woodgroove.com.enterprise PersonService.wsdl AddressType.xsd GenderType.xsd Person.xsd Pers onServiceMessages.xsd /l:cs
|
An abbreviated version of the code that is subsequently generated is shown here:
Example 5.
[System.CodeDom.Compiler.GeneratedCodeAttribute ("System.ServiceModel", "4.0.0.0")] [System.ServiceModel.ServiceContractAttribute (Namespace="http://schemas.example.org/enterprise/service/v1", ConfigurationName="schemas.example.org.enterprise. PersonServiceInterface")]
public interface PersonServiceInterface { [System.ServiceModel.OperationContractAttribute (Action="http://schemas.example.org/enterprise/service/v1:getPer- sonIn", ReplyAction="*")] [return: System.ServiceModel.MessageParameterAttribute(Name="person")] schemas.example.org.enterprise.person GetPerson(long personId); [System.ServiceModel.OperationContractAttribute(Action="http://schemas .example.org/enterprise/service/v1:getAllPersonsIn", ReplyAction="*")] [return:System.ServiceModel.MessageParameterAttribute (Name="person")] schemas.example.org.enterprise.personList GetAllPersons(); [System.ServiceModel.OperationContractAttribute(Action="http://schemas .example.org/enterprise/service/v1:updatePersonIn", ReplyAction="*")] [return:System.ServiceModel.MessageParameterAttribute (Name="success")] bool UpdatePerson(schemas.example.org.enterprise.person updatablePerson); } [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime. Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute (Name="person", Namespace="http://schemas.example.org/enterprise/ models/v1")]
public partial class person : object, System.Runtime.Serialization.IExtensibleDataObject... [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime. Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="addressType" ,Namespace="http://schemas.example.org/enterprise/models/v1")]
public partial class addressType : object, System.Runtime. Serialization.IextensibleDataObject... [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime. Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="gender- Type",Namespace="http://schemas.example.org/enterprise/models/v1")]
public enum genderType : int... [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime. Serialization", "4.0.0.0")] [System.Runtime.Serialization.CollectionDataContractAttribute (Name="personList", Namespace="http://schemas.example.org/enterprise/models/v1", ItemName="person")]
public class personList : System.Collections.Generic.List<schemas.example.org.enterprise. person>... [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public interface PersonServiceInterfaceChannel : schemas.example.org.enterprise.PersonServiceInterface, System. ServiceModel.IclientChannel... [System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class PersonServiceInterfaceClient : System. ServiceModel.ClientBase<schemas.example.org.enterprise. PersonServiceInterface>, schemas.example.org.enterprise. PersonServiceInterface
|
What
we have here consists of an interface for the actual service and
classes for message types. This is convenient as it establishes a
structure compliant with the service contract (as per logic-to-contract
coupling). That the service is generated as an interface makes sense as
you would not want to change the generated code in order to implement
your service. This further allows you to start writing code to implement
the service without having to worry about the code being overwritten if
regenerating the service code should become necessary.
The commonly used WCF attributes we should all be familiar with by now are ServiceContract, OperationContract, DataContract, and DataMember.
When your service is ready to
be deployed, WCF will allow you to point consumers to the previously
created WSDL file rather than an automatically generated WSDL file from WCF (the files will look different). To do this simply add this XML statement to the service configuration file:
<serviceMetadata httpGetEnabled="true"
externalMetadataLocation="\path\personservice.wsdl"/>
Generating WCF Service Code Using WSCF.blue
WSCF.blue is a third
party Visual Studio add-in that was created in support of contract-first
development. This tool allows you to create WSDL files and generate WCF
code from the WSDL markup code. WSCF.blue is a wizard-driven tool and
we will therefore be briefly stepping through its screens in this
section.
You begin by right-clicking on the PersonServiceMessages.xsd file and choosing the “Create WSDL Interface Description” option (Figure 2).
The next set of wizard dialogs are labeled according to steps, as follows:
Step 1– The first step lets you specify basic settings, like service name, namespace and documentation (Figure 3).
Step 2– You can add message schemas (other than the ones that are in the file that we right-clicked), as shown in Figure 4. Note that in our particular example we do not need to do carry out this step.
Step 3– You can specify Web service operations, which can also be automatically inferred by the tool (Figure 5).
Step 4– You can determine the message parameters for the individual operations (Figure 6).
Step 5– Here you are presented with a checkbox that allows you to directly move on to a code generation dialog (Figure 7).
The final dialog will be
pre-loaded with the newly created WSDL file and will allow you to choose
from generating a client-proxy or a service-stub. For our example, we
will be proceeding with the service-stub.
The generated code looks familiar, with the exception that the data classes are not serialized using the DataContractSerializer but rather the XmlSerializer. This is deliberate as XmlSerializer supports a wider range of schema constructs.
Example 6.
[System.CodeDom.Compiler.GeneratedCodeAttribute ("System.ServiceModel", "3.0.0.0")] [System.ServiceModel. ServiceContractAttribute(Namespace="schemas.example.org.enterprise. service.v1", ConfigurationName="IPersonServiceWCFBlue")] public interface IPersonServiceWCFBlue { // CODEGEN: Generating message contract since the wrapper namespace (http://schemas.example.org/enterprise/service/v1) of message GetPersonRequest does not match the default value (schemas.example.org.enterprise.service.v1) [System.ServiceModel. OperationContractAttribute(Action="schemas.example.org.enterprise. service.v1:getPersonIn", ReplyAction="*")] [System.ServiceModel. XmlSerializerFormatAttribute(SupportFaults=true)]
GetPersonResponse GetPerson(GetPersonRequest request); // CODEGEN: Generating message contract since the wrapper namespace (http://schemas.example.org/enterprise/service/v1) of message GetAllPersonsRequest does not match the default value (schemas. example.org.enterprise.service.v1) [System.ServiceModel. OperationContractAttribute(Action="schemas.example.org.enterprise. service.v1:getAllPersonsIn", ReplyAction="*")] [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
GetAllPersonsResponse GetAllPersons(GetAllPersonsRequest request); // CODEGEN: Generating message contract since the wrapper namespace (http://schemas.example.org/enterprise/service/v1) of message UpdatePersonRequest does not match the default value (schemas. example.org.enterprise.service.v1) [System.ServiceModel. OperationContractAttribute(Action="schemas.example.org.enterprise. service.v1:updatePersonIn", ReplyAction="*")] [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)] UpdatePersonResponse UpdatePerson(UpdatePersonRequest request); }
|
Generating ASMX Service Code Using WSCF.classic
The
WSCF.blue tool has a predecessor called WSCF.classic. This version of
the tool is very similar to the current version, but with one big
difference: the WSCF.classic tool instead generates ASMX service code.
The code generation dialog looks similar to the one in the WSCF.blue but
the options are different as shown in Figure 9.
The generated code is annotated with the familiar WebMethod attribute of ASMX:
Example 7.
Version=0.7.6319.1 #endregion namespace PersonServiceWCFBlue { using System.Diagnostics; using System.Web.Services; using System.ComponentModel; using System.Web.Services.Protocols; using System; using System.Xml.Serialization; using System.Web; /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Web.Services", "2.0.50727.4016")] [System.Web.Services.
WebServiceAttribute(Namespace="schemas.example.org.enterprise. service.v1")] [System.Web.Services.WebServiceBindingAttribute(Name= "BasicHttpBinding_PersonServiceWCFBlueInterface", Namespace="schemas.example.org.enterprise.service.v1")] public partial class BasicHttpBinding_PersonServiceWCFBlueInterface : System.Web.Services.WebService, IBasicHttpBinding_PersonServiceWCF- BlueInterface {public BasicHttpBinding_PersonServiceWCFBlueInterface() { } /// <remarks/> [System.Web.Services.WebMethodAttribute()] [System.Web.Services. Protocols.SoapDocumentMethodAttribute("schemas.example.org. enterprise.service.v1:getPersonIn", RequestNamespace="http://schemas.example.org/enterprise/service/v1", ResponseNamespace="http://schemas.example.org/enterprise/service/v1", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle. Wrapped, Binding="BasicHttpBinding_PersonServiceWCFBlueInterface")] [return: System.Xml.Serialization.XmlElementAttribute("person", IsNullable=true)] public virtual person GetPerson([System.Xml.Serialization. XmlElementAttribute(DataType="positiveInteger", ElementName= "personId")] string personId) {throw new System.NotImplementedException();} /// <remarks/> [System.Web.Services.WebMethodAttribute()] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("schemas. example.org.enterprise.service.v1:getAllPersonsIn", RequestNamespace="http://schemas.example.org/enterprise/service/v1", ResponseNamespace="http://schemas.example.org/enterprise/service/v1", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle. Wrapped, Binding="BasicHttpBinding_PersonServiceWCFBlueInterface")] [return: System.Xml.Serialization.XmlArrayAttribute("person", IsNullable=true)] [return: System.Xml.Serialization.XmlArrayItemAttribute(Namespace="http:// schemas.example.org/enterprise/models/v1", IsNullable=false)] public virtual person[] GetAllPersons() {throw new System.NotImplementedException();} /// <remarks/> [System.Web.Services.WebMethodAttribute()] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("schemas. example.org.enterprise.service.v1:updatePersonIn", RequestNamespace="http://schemas.example.org/enterprise/service/v1", ResponseNamespace="http://schemas.example.org/enterprise/service/v1", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle. Wrapped, Binding="BasicHttpBinding_PersonServiceWCFBlueInterface")] [return: System.Xml.Serialization.XmlElementAttribute("success")] public virtual bool UpdatePerson([System.Xml.Serialization. XmlElementAttribute(IsNullable=true, ElementName="updatablePerson")] person updatablePerson) {throw new System.NotImplementedException();} }}
|