'WCF LOB Adapter SDK' metadata object model

WCF LOB Adapter SDK provides Metadata Object Model to define a contract for an operation. This object model is used within IMetadataResolver implemented classes. The purpose of this model is to generate the XML Schema representation of the parameters and results of an operation during dynamic contract generation. The metadata resolver is called by the Contract (WSDL) Builder component of the adapter SDK, once 1 or more operations are selected by the adapter consumer. The metadata resolver expects an operation identifier (typically the SOAP action) and resolves the metadata for the operation based on this identifier.

The following class diagrams show the classes provided by WCF LOB Adapter SDK for defining the syntax of an operation supported by the adapter.

Operation Metadata

Qualified Type

Type Metadata

 

Operation Metadata

· OperationMetadata is an abstract class that represents metadata for an operation

· ParameterizedOperationMetadata inherits from OperationMetadata and can be used to define a parameterized operation

o OperationParameter (derives from QualifiedTypeContainer)

§ OperationParameterDirection (specifies the direction of the parameter)

§ Can provide SimpleQualifiedType or a ComplexQualifiedType as an operation parameter

o OperationResult (derives from QualifiedTypeContainer)

§ Can provide SimpleQualifiedType or a ComplexQualifiedType or Empty as an operation result

§ If it is set to ‘null’, then the generated operation contract has IsOneWay attribute with a void result.

§ If it is set to ‘OperationResult.Empty’, then the generation contract has a void result.

· In scenarios where ParameterizedOperationMetdata doesn’t suffice or there is a need to get input and output XML Schema objects from a pre-defined schema file, create your own subclass of OperationMetadata. Implement methods ExportInputXmlSchema and ExportOutputXmlSchema to provide input and output XML Schema metadata for this operation. This may be the case when the back-end system already has metadata available in XML Schema format. If own input and output XML schema is provided, the control never goes to type metadata for type resolution.

Qualified Types

· QualifiedTypeContainer is the link between OperationMetadata and TypeMetadata. It can contain either a SimpleQualifiedType or ComplexQualifiedType. Both SimpleQualifiedType and ComplexQualifiedType derive from QualifiedType.

· If ParameterizedOperationMetadata is being used and one of the parameters is a complex type, then the ID defined in ComplexQualifiedType is used to resolve the type. The control flows over to ResolveTypeMetadata() operation.

· QualifiedType is an abstract class containing static functions to return the instances of SimpleQualifiedType or ComplexQualifiedType for common types.

Type Metadata

· TypeMetadata is an abstract class that represents metadata for a type.

· StructuredTypeMetadata inherits from TypeMetadata and can be used to defined a data structure that contains type members.

· The TypeMember in turn can either be a SImpleQualifedType or a ComplexQualifiedType.

· In scenarios where StructuredTypeMetadata doesn’t suffice or there is a need to get the type definition from an existing XML Schema file, create your own subclass of TypeMetadata. Implement method ExportXmlSchema to provide the XML schema metadata for this type.

Code Snippet – double Add(double n1, double n2)

Here is an example of defining a simple operation signature using the metadata provided by the adapter SDK.

Note: This sample snippet is for illustrative purposes only to show the features of the metadata object model. If you want to support 50+ operations in your adapter, you don’t want to define these 50 operations one by one using this model. Ideally, your target system provides a way to dynamically generate the operations and types. This way if your target system is changing or large, the developer doesn’t have to define the service contracts individually. Keep in mind, WCF provides an intuitive programming model to create service oriented interfaces, by starting from a CLR class and augmenting it with various metadata attributes (e.g. ServiceContract, OperationContract, DataContract, DataMember, etc.). WCF LOB Adapter SDK is meant for cases, where the adapter can generate those interfaces dynamically: Metadata Object Model à XML Schema à CLR Object. For example, for a database adapter, an operation metadata instance can be generated for each available stored procedures. The tables can be represented using TypeMetadata, where column can be a TypeMember.

        public OperationMetadata ResolveOperationMetadata(string operationId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved)

        {

            extraTypeMetadataResolved = null;

  // for illustrative purposes show how to resolve one operation here – usually it should be more dynamic than this

            ParameterizedOperationMetadata opMetadata = new ParameterizedOperationMetadata(operationId, "");

            // Setting opMetadata.DisplayName is mandatory, otherwise metadata won't be generated

            opMetadata.OperationNamespace = SampleAdapter.SERVICENAMESPACE;

            opMetadata.OperationGroup = "SampleContract";

            switch (operationId)

   {

                case "/Calculator/Add":

                    // Syntax: double Add(double n1, double n2)

     opMetadata.DisplayName = "Add";

                    // Create Parameters

                    OperationParameter n1 = new OperationParameter("n1", OperationParameterDirection.In, QualifiedType.DoubleType, false);

                    n1.IsOptional = false;

                    OperationParameter n2 = new OperationParameter("n2", OperationParameterDirection.In, QualifiedType.DoubleType, false);

                    n2.IsOptional = false;

                    opMetadata.Parameters.Add(n1);

                    opMetadata.Parameters.Add(n2);

                    // Create Result

                    opMetadata.OperationResult = new OperationResult(QualifiedType.DoubleType, false);

                    break;

                default:

                    throw new AdapterException("Invalid operation " + operationId);

            }

            return opMetadata;

       }

Generated WSDL / XML Schema

You can read this post to learn how to generate the WSDL for a given set of operations. Use Svcutil.Exe to get CLR objects from this WSDL.

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

<wsdl:definitions xmlns:tns="sample://My.Samples.CodeSnippets" xmlns:wsaw="https://www.w3.org/2006/05/addressing/wsdl" xmlns:ns2="sample://My.Samples.CodeSnippets" targetNamespace="sample://My.Samples.CodeSnippets" xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/">

  <wsdl:types>

    <schema elementFormDefault="qualified" targetNamespace="sample://My.Samples.CodeSnippets" version="1.0" xmlns="https://www.w3.org/2001/XMLSchema">

      <element name="Add">

        <annotation>

          <documentation>

            <doc:action xmlns:doc="https://schemas.microsoft.com/servicemodel/adapters/metadata/documentation">/Calculator/Add</doc:action>

          </documentation>

        </annotation>

        <complexType>

          <sequence>

            <element minOccurs="1" maxOccurs="1" name="n1" type="double" />

            <element minOccurs="1" maxOccurs="1" name="n2" type="double" />

          </sequence>

        </complexType>

      </element>

      <element name="AddResponse">

        <annotation>

          <documentation>

            <doc:action xmlns:doc="https://schemas.microsoft.com/servicemodel/adapters/metadata/documentation">/Calculator/Add/response</doc:action>

          </documentation>

        </annotation>

        <complexType>

          <sequence>

            <element minOccurs="1" maxOccurs="1" name="AddResult" type="double" />

          </sequence>

        </complexType>

      </element>

    </schema>

  </wsdl:types>

  <wsdl:message name="SampleContract_Add_InputMessage">

    <wsdl:part name="parameters" element="ns2:Add" />

  </wsdl:message>

  <wsdl:message name="SampleContract_Add_OutputMessage">

    <wsdl:part name="parameters" element="ns2:AddResponse" />

  </wsdl:message>

  <wsdl:portType name="SampleContract">

    <wsdl:operation name="Add">

      <wsdl:input wsaw:Action="/Calculator/Add" message="ns2:SampleContract_Add_InputMessage" />

      <wsdl:output wsaw:Action="/Calculator/Add/response" message="ns2:SampleContract_Add_OutputMessage" />

    </wsdl:operation>

  </wsdl:portType>

</wsdl:definitions>

Sample 2 – SampleComplexObject[] EchoComplexObject(SampleComplexObject complexObject)

 

Let’s now take an example of a parameterized operation that uses a complex type metadata. The SampleComplexObject contains a nested complex type called Greeting along with a bunch of simple types.

Source Code Listing – AdapterMetadataResolver.cs

        public OperationMetadata ResolveOperationMetadata(string operationId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved)

        {

  extraTypeMetadataResolved = null;

            // for illustrative purposes show how to resolve one operation here

            ParameterizedOperationMetadata opMetadata = new ParameterizedOperationMetadata(operationId, "");

            // Setting opMetadata.DisplayName is mandatory, otherwise metadata won't be generated

            opMetadata.OperationNamespace = SampleAdapter.SERVICENAMESPACE;

            opMetadata.OperationGroup = "SampleContract";

            switch (operationId)

            {

                case "/Echo/EchoComplexObject":

                    // Syntax: SampleComplexObject[] EchoComplexObject(SampleComplexObject complexObject)

                    opMetadata.DisplayName = "EchoComplexObject";

                    // Create Parameters

                    ComplexQualifiedType myEchoObjectQT = new ComplexQualifiedType("/Echo/SampleComplexObject");

                    OperationParameter complexObject = new OperationParameter("complexObject", OperationParameterDirection.In, myEchoObjectQT, false);

                    opMetadata.Parameters.Add(complexObject);

                    // Create Result

                    opMetadata.OperationResult = new OperationResult(myEchoObjectQT, true);

                    break;

                default:

                    throw new AdapterException("Invalid operation " + operationId);

            }

            return opMetadata;

        }

        public TypeMetadata ResolveTypeMetadata(string typeId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved)

        {

            extraTypeMetadataResolved = null;

            switch (typeId)

            {

                case "/Echo/SampleComplexObject":

                    // this object has 5 members

                    // 1 is of type guid

                    // 2 is of type enum

                    // 3 is of type char[]

                    // 4 is of type TimeSpan

                    // 5 is of type Greeting

            // 6 is of type float with a facet

                    SimpleQualifiedType decimalSQT = new SimpleQualifiedType(XmlTypeCode.Decimal);

                    XmlSchemaFacet facet = new XmlSchemaFractionDigitsFacet();

                    facet.Value = "5";

                    decimalSQT.XmlSchemaFacets.Add(facet);

                    StructuredTypeMetadata complexObjTM = new StructuredTypeMetadata(typeId, "SampleComplexObject");

                    complexObjTM.TypeNamespace = SampleAdapter.SERVICENAMESPACE + "/types";

                    complexObjTM.Members.Add(new TypeMember("uniqueID", QualifiedType.GuidType, false));

                    complexObjTM.Members.Add(new TypeMember("salutation", new ComplexQualifiedType("/Echo/Enum/Salutation"), false));

                    complexObjTM.Members.Add(new TypeMember("text", QualifiedType.CharType, true));

                    complexObjTM.Members.Add(new TypeMember("duration", QualifiedType.DurationType, false));

                    complexObjTM.Members.Add(new TypeMember("greeting", new ComplexQualifiedType("/Echo/Greeting"), false));

                    complexObjTM.Members.Add(new TypeMember("amount", decimalSQT, false));

                    return complexObjTM;

                case "/Echo/Enum/Salutation":

                    EnumTypeMetadata salutationTM = new EnumTypeMetadata(typeId, "Salutation", new String[] { "Mr.", "Mrs.", "Miss" });

                    salutationTM.TypeNamespace = SampleAdapter.SERVICENAMESPACE + "/types";

                    return salutationTM;

                case "/Echo/Greeting":

                    // Use a pre-defined schema to generate metadata for this type

                    TypeMetadata typeMetadata = new MyCustomTypeMetadata(typeId, "Greeting");

                    typeMetadata.TypeNamespace = SampleAdapter.SERVICENAMESPACE + "/PreDefinedTypes";

                    return typeMetadata;

                default:

                    throw new AdapterException("Invalid type " + typeId);

   }

        }

In this sample, instead of defining the type Greeting using StructuredTypeMetadata, I would like to use an existing schema representation. In that case, I define my own subclass called MyCustomTypeMetadata and provide the XML Schema for the Greeting type.

Generated WSDL / XML Schema

I won’t show the entire WSDL here, but just the related wsdl:types schema section for SampleComplexObject generated by the adapter, given the metadata definition above. Notice that the schema definition for Greeting complex type was retrieved using the custom type metadata from an existing XSD file.

<wsdl:types>

<schema xmlns:ns4="https://schemas.microsoft.com/2003/10/Serialization/" xmlns:ns5="sample://My.Samples.CodeSnippets/PreDefinedTypes" xmlns:ns3="sample://My.Samples.CodeSnippets/types" xmlns:array="https://schemas.microsoft.com/2003/10/Serialization/Arrays" elementFormDefault="qualified" targetNamespace="sample://My.Samples.CodeSnippets/types" version="1.0" xmlns="https://www.w3.org/2001/XMLSchema">

<import namespace="https://schemas.microsoft.com/2003/10/Serialization/" />

<import namespace="https://schemas.microsoft.com/2003/10/Serialization/Arrays" />

<import namespace="sample://My.Samples.CodeSnippets/PreDefinedTypes" />

<complexType name="SampleComplexObject">

<sequence>

<element minOccurs="1" maxOccurs="1" name="uniqueID" type="ns4:guid" />

<element minOccurs="1" maxOccurs="1" name="salutation" type="ns3:Salutation" />

<element minOccurs="1" maxOccurs="1" name="text" nillable="true" type="array:ArrayOfchar" />

<element minOccurs="1" maxOccurs="1" name="duration" type="ns4:duration" />

<element minOccurs="1" maxOccurs="1" name="greeting" type="ns5:Greeting" />

<element minOccurs="1" maxOccurs="1" name="amount">

<simpleType>

<restriction base="decimal">

<fractionDigits value="5" />

</restriction>

</simpleType>

</element>

</sequence>

</complexType>

<element name="SampleComplexObject" nillable="true" type="ns3:SampleComplexObject" />

<simpleType name="Salutation">

<restriction base="string">

<enumeration value="Mr." />

<enumeration value="Mrs." />

<enumeration value="Miss" />

</restriction>

</simpleType>

<element name="Salutation" type="ns3:Salutation" />

<complexType name="ArrayOfSampleComplexObject">

<sequence>

<element minOccurs="1" maxOccurs="unbounded" name="SampleComplexObject" type="ns3:SampleComplexObject" />

</sequence>

</complexType>

<element name="ArrayOfSampleComplexObject" nillable="true" type="ns3:ArrayOfSampleComplexObject" />

</schema>

<schema xmlns:b="https://schemas.microsoft.com/BizTalk/2003" xmlns="sample://My.Samples.CodeSnippets/PreDefinedTypes" elementFormDefault="qualified" targetNamespace="sample://My.Samples.CodeSnippets/PreDefinedTypes">

<element name="greeting" xmlns:q1="sample://My.Samples.CodeSnippets/PreDefinedTypes" type="q1:Greeting" />

<complexType name="Greeting">

<sequence>

<element name="address" xmlns:q2="sample://My.Samples.CodeSnippets/PreDefinedTypes" type="q2:UsAddress" />

<element name="text" type="string" />

<element minOccurs="0" maxOccurs="1" name="gender" type="string" />

</sequence>

</complexType>

<simpleType name="PostalCode">

<restriction base="positiveInteger">

<pattern value="\d{5}" />

</restriction>

</simpleType>

<complexType name="UsAddress">

<sequence>

<element minOccurs="1" maxOccurs="1" name="street1" nillable="true" type="string" />

<element minOccurs="0" maxOccurs="1" name="street2" type="string" />

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

<element name="zip" xmlns:q3="sample://My.Samples.CodeSnippets/PreDefinedTypes" type="q3:PostalCode" />

</sequence>

</complexType>

</schema>

</wsdl:types>

Summary

 

· Operation/Type Metadata Object Model is used for contract generation

· ParameterizedOperationMetadata class can be used for most common operation representations

· StructuredTypeMetadata can be used for most common types representations

· Metadata object model is extensible by allowing the adapter developer to provide own XML Schema representations for its members using Export*XmlSchema methods.