Providing custom XML Schema files for resolving operation and type metadata
One of the frequently asked questions that I get from our TAP customers is about an ability to provide own schema files for resolving the metadata. In this post, I provide a simple example of accomplishing that. I will add more details in future posts as necessary.
At design-time, once the adapter consumer has selected one or more operations, the Contract Generation utility (i.e. Add Adapter Service Reference tool) makes a call into the adapter to resolve the metadata to return appropriate WSDL and XML Schema. This WSDL and XML Schema are used by the tool to then generate appropriate metadata (.NET code for .net project and Schema files for BizTalk projects) files. The key method at work is adapter's implementation class for IMetadataResolver with methods ResolveOperationMetadata and ResolveTypeMetadata. See HelloWorldAdapterMetadataResolverHandler sample class at the end of this post.
OperationMetadata and TypeMetadata classes were re-factored in the February CTP refresh of the WCF LOB Adapter SDK. OperationMetadata and TypeMetadata are abstract classes now. Use ParameterizedOperationMetadata and StructuredTypeMetadata, where the original metadata classes were being used. In order to provide custom Xml Schemas, Adapter Writer can provide custom implementation by sub-classing OperationMetadata and TypeMetadata classes.
In this sample, instead of defining a custom type called Greeting using StructuredTypeMetadata, I would like to use an existing schema representation. In that case, I define my own subclass from TypeMetadata and override the ExportXmlSchema method to provide the XML Schema for the Greeting type.
class MyCustomTypeMetadata : TypeMetadata
{
public MyCustomTypeMetadata(string typeId, string typeName) : base(typeId, typeName)
{
}
/// <summary>
/// Override the base ExportXmlSchema and provide own custom
/// XML Schema.
/// </summary>
/// <param name="schemaExportContext"></param>
/// <param name="metadataLookup"></param>
/// <param name="timeout"></param>
public override void ExportXmlSchema(XmlSchemaExportContext schemaExportContext, MetadataLookup metadataLookup, TimeSpan timeout)
{
if (schemaExportContext == null)
{
throw new AdapterException("schemaExportContext is null");
}
// Find a way to either read in a schema file or create XmlSchema
// object yourself
// for this example, use the type name to load a schema
XmlReader reader = XmlReader.Create(@"c:\temp\greeting.xsd");
XmlSchema schema = XmlSchema.Read(reader, null);
if (!IsComplexTypeAlreadyDefined(schemaExportContext.SchemaSet, schema))
{
schemaExportContext.SchemaSet.Add(schema);
schemaExportContext.NamespacePrefixSet.Add("mytypes", "sample://My.Samples.CodeSnippets/PreDefinedTypes");
}
reader.Close();
}
/// <summary>
/// Helper function to see if the schema is already defined in the
/// XmlSchemaSet.
/// </summary>
/// <param name="oldschemaset"></param>
/// <param name="newschema"></param>
/// <returns></returns>
public static bool IsComplexTypeAlreadyDefined(XmlSchemaSet oldschemaset, XmlSchema newschema)
{
// ensure correct namespace was defined in the passed-in schema
foreach (XmlSchema schema in oldschemaset.Schemas(newschema.TargetNamespace))
{
foreach (XmlSchemaObject newschemaObject in newschema.Items)
{
if (newschemaObject is XmlSchemaComplexType)
{
//check for the definition of complex type in the schemaset
foreach (XmlSchemaObject schemaObject in schema.Items)
{
XmlSchemaComplexType complexType = schemaObject as XmlSchemaComplexType;
// Definition of this Complex Type already exists
if (complexType != null && String.Compare(complexType.Name, ((XmlSchemaComplexType)newschemaObject).Name, false, System.Globalization.CultureInfo.InvariantCulture) == 0)
return true;
}
}
}
}
return false;
}
public override bool CanSetDefaultValue
{
get { return false; }
}
public override System.Xml.XmlReader CreateXmlReader(AdapterDataReader dataReader)
{
return null;
}
public override System.Xml.XmlDictionaryWriter CreateXmlWriter(AdapterDataWriter dataWriter)
{
return null;
}
}
Greeting.xsd
<xsd:schema xmlns:b="https://schemas.microsoft.com/BizTalk/2003" xmlns="sample://My.Samples.CodeSnippets/PreDefinedTypes" elementFormDefault="qualified" targetNamespace="sample://My.Samples.CodeSnippets/PreDefinedTypes" xmlns:xsd="https://www.w3.org/2001/XMLSchema">
<xsd:element name="greeting" type="Greeting" />
<xsd:complexType name="Greeting">
<xsd:sequence>
<xsd:element name="address" type="UsAddress" />
<xsd:element name="text" type="xsd:string" />
<xsd:element minOccurs="0" maxOccurs="1" name="gender" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="PostalCode">
<xsd:restriction base="xsd:positiveInteger">
<xsd:pattern value="\d{5}" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="UsAddress">
<xsd:sequence>
<xsd:element minOccurs="1" maxOccurs="1" name="street1" nillable="true" type="xsd:string" />
<xsd:element minOccurs="0" maxOccurs="1" name="street2" type="xsd:string" />
<xsd:element minOccurs="1" maxOccurs="1" name="city" type="xsd:string" />
<xsd:element name="zip" type="PostalCode" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
Similarly, the ExportInputXmlSchema and ExportOutputXmlSchema can be overriden in the Adapter’s Operation Metadata to provide custom schema files. The Adapter Writer can use the metadataLookup.GetOperationDefinitionFromInputMessageAction to get data for looking up and using the appropriate schema files.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.ServiceModel.Adapters.Common;
namespace Microsoft.WCF.Samples.Adapters
{
public class SayHelloWorldOperationMetadata : OperationMetadata
{
// Constructor
public SayHelloWorldOperationMetadata(string operationId, string displayName) : base(operationId, displayName);
{
}
/// <summary>
/// Use the request message schema for resolving request message for an operation.
/// </summary>
/// <param name="exportContext"></param>
/// <param name="metadataLookup"></param>
/// <param name="timespan"></param>
public override void ExportInputXmlSchema(XmlSchemaExportContext exportContext, MetadataLookup metadataLookup, TimeSpan timespan)
{
base.ExportXmlSchema(exportContext, metadataLookup, timespan);
XmlReader reader = XmlReader.Create("SayHelloWorldRequestMessage.xsd");
XmlSchema schema = XmlSchema.Read(reader, null);
if (!MetadataOperation.IsComplexTypeAlreadyDefined(exportContext.SchemaSet, schema))
schemaExportContext.SchemaSet.Add(schema);
reader.Close();
}
/// <summary>
/// Use the response message schema for resolving response message for an operation.
/// </summary>
/// <param name="exportContext"></param>
/// <param name="metadataLookup"></param>
/// <param name="timespan"></param>
public override void ExportOutputXmlSchema(XmlSchemaExportContext exportContext, MetadataLookup metadataLookup, TimeSpan timespan)
{
base.ExportXmlSchema(exportContext, metadataLookup, timespan);
XmlReader reader = XmlReader.Create("SayHelloWorldResponseMessage.xsd");
XmlSchema schema = XmlSchema.Read(reader, null);
if (!MetadataOperation.IsComplexTypeAlreadyDefined(exportContext.SchemaSet, schema))
schemaExportContext.SchemaSet.Add(schema);
reader.Close();
}
}
}
The custom operation metadata and type metadata classes are used in the Adapter’s metadata resolver handler class.
/// -----------------------------------------------------------------------------------------------------------
/// Module : HelloWorldAdapterMetadataResolverHandler.cs
/// Description : Metadata Resolver Handler class which implements the HandlerBase and
/// IMetadataResolverHandler interface
/// -----------------------------------------------------------------------------------------------------------
#region Using Directives
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.ServiceModel.Adapters.Common;
using System.Xml.Schema;
#endregion
namespace Microsoft.WCF.Samples.Adapters
{
public class HelloWorldAdapterMetadataResolverHandler : HelloWorldAdapterHandlerBase, IMetadataResolverHandler
{
public HelloWorldAdapterMetadataResolverHandler(HelloWorldAdapterConnection connection
, MetadataLookup metadataLookup)
: base(connection, metadataLookup)
{
}
#region IMetadataResolver Members
public bool IsOperationMetadataValid(string operationId, DateTime lastUpdatedTimestamp, TimeSpan timeout)
{
if ("SayHelloWorld".Equals(operationId))
{
return true;
}
return false;
}
public bool IsTypeMetadataValid(string typeId, DateTime lastUpdatedTimestamp, TimeSpan timeout)
{
return true;
}
public OperationMetadata ResolveOperationMetadata(string operationId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved)
{
extraTypeMetadataResolved = null;
if ("Hello/SayHelloWorld".Equals(operationId))
{
// USE CUSTOM SCHEMA
if (useCustomSchema)
{
SayHelloWorldOperationMetadata om = new SayHelloWorldOperationMetadata("Hello/SayHelloWorld", "SayHelloWorld");
return om;
}
// USE LOB ADAPTER SDK METADATA OBJECT MODEL
else
{
ParameterizedOperationMetadata om = new ParameterizedOperationMetadata("Hello/SayHelloWorld", "SayHelloWorld");
om.OperationNamespace = HelloWorldAdapter.SERVICENAMESPACE;
// TODO - the node ID is not set
// syntax: String SayHelloWorld( String aName );
OperationParameter parm1 = new OperationParameter("aName", OperationParameterDirection.In, new SimpleQualifiedType(XmlTypeCode.String), false);
parm1.Description = "Hello World is said by this name.";
// Expected result: aName says Hello World
OperationResult result = new OperationResult(new SimpleQualifiedType(XmlTypeCode.String), false);
om.Parameters = new List<OperationParameter>();
om.Parameters.Add(parm1);
om.OperationResult = result;
return om;
}
}
return null;
}
public TypeMetadata ResolveTypeMetadata(string typeId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved)
{
extraTypeMetadataResolved = null;
return null;
}
#endregion IMetadataResolver Members
}
}
Comments
Anonymous
March 28, 2007
The comment has been removedAnonymous
March 30, 2007
Hi Aman, Thanks for your comment. I will post a sample on this blog shortly. We have added some documentation related to Metadata Interfaces and Metadata Object Model in the WCF LOB Adapter SDK Beta 2 Release’s help file ({Install Folder}DocumentsWCFLOBAdapterSDK.chm). If you have any urgent queries or are blocked at any time, please send your TAP Program Manager an email and he can get us in touch as well. Time permitting, I will also keep blogging about new and most wanted topics. :-) I would also like to hear your feedback on the product, so we can keep improving. Regards, SonuAnonymous
June 20, 2007
WCF LOB Adapter SDK provides Metadata Object Model to define a contract for an operation. This objectAnonymous
June 20, 2007
Scenario: Adapter Developer needs to define an operation signature using XML Schema types not directlyAnonymous
July 09, 2007
The comment has been removedAnonymous
July 10, 2007
Mike, Can you provide more detail about the error? It will help me understand the problem better.