Skip to content

Instantly share code, notes, and snippets.

@dlachowicz-mw
Created October 17, 2018 15:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dlachowicz-mw/ce1e5336e2066792f5c92a74dbfece50 to your computer and use it in GitHub Desktop.
Save dlachowicz-mw/ce1e5336e2066792f5c92a74dbfece50 to your computer and use it in GitHub Desktop.
This class represents a SOAP extension point in our SOAP DTOs, and helps preserve an "agile" API versioning strategy.
using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Cayan.Kernel.SoapMessaging
{
/// <summary>
/// This class represents a SOAP extension point in our SOAP DTOs, and helps preserve
/// an "agile" API versioning strategy. In this case, "agile versioning" means that we
/// can make additive, non-breaking changes to our message contracts. I.e. we can add new elements
/// to our responses and/or add new optional elements to our request objects, without
/// having to version the API.
///
/// The challenge we run into is many API clients and frameworks (eg. JAXB) perform strict WSDL validation.
/// If the contract changes because we (eg.) add new elements to a response message that a client is not required
/// to understand, this will break certain clients.
///
/// This class achieves its goal by:
///
/// 1. Presenting an "empty" schema to the WSDL generation tools. The WSDL generation tools
/// believe that this class has no child elements.
/// 2. Adding dynamic child elements to the SOAP message body at runtime, thus strictly breaking
/// the WSDL contract.
///
/// By always including strictly non-confirming elements in the SOAP response messages, this
/// requires our API clients to be strict in what they send, and liberal in what they receive
/// (Postel's Law / Robustness Principle).
/// </summary>
[XmlRoot(ElementName = "SoapExtensionPointElement", DataType = "SoapExtensionPointType", Namespace = "http://merchantware.net/anonymous.xsd", IsNullable = true)]
[XmlSchemaProvider("GetSoapExtensionPointSchema")]
public sealed class SoapExtensionPoint : IXmlSerializable
{
/// <summary>
/// Implements IXmlSerializable::GetSchema()
/// </summary>
/// <returns>A null XmlSchema, as required by IXmlSerializable</returns>
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
/// Implements IXmlSerializable::ReadXml(). Skips over the SoapExtensionPoint element, and
/// advance to the next element.
/// </summary>
/// <param name="reader">An XmlReader</param>
public void ReadXml(XmlReader reader)
{
reader.Skip();
}
/// <summary>
/// Generate between [1, MAX_ANONYMOUS_CHILD_ELEMENTS] child elements
/// </summary>
public const int MAX_ANONYMOUS_CHILD_ELEMENTS = 2;
/// <summary>
/// Implements IXmlSerializable::WriteXml(). This class adds an arbitrary number of child
/// elements whose element names and values are both GUIDs. Using GUIDs that differ with each
/// response prevents clients from hardcoding certain expectations into their contracts.
/// </summary>
/// <param name="writer">An XmlWriter</param>
public void WriteXml(XmlWriter writer)
{
Random r = new Random();
int childElementsToAdd = r.Next(1, MAX_ANONYMOUS_CHILD_ELEMENTS + 1);
for (int i = 0; i < childElementsToAdd; i++)
{
var guid = Guid.NewGuid().ToString();
writer.WriteElementString(String.Format("_{0}", guid), guid);
}
}
/// <summary>
/// Our SoapExtensionPoint's namespace.
/// </summary>
private readonly static string anonymousSchemaNamespace = "http://merchantware.net/SoapExtensionPoint.xsd";
/// <summary>
/// Our anonymous schema. We want to convince the .NET runtime that this element doesn't have any children,
/// but then dynamically add children elements at runtime.
/// </summary>
private readonly static string anonymousSchema = String.Format(@"<xs:schema xmlns:tns=""{0}"" elementFormDefault=""qualified"" targetNamespace=""{0}"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""SoapExtensionPointElement"" type=""tns:SoapExtensionPointType"" />
<xs:complexType name=""SoapExtensionPointType"" />
</xs:schema>", anonymousSchemaNamespace);
/// <summary>
/// Provides the schema required by XmlSchemaProvider. This is required because, otherwise, the derived WSDL
/// will permit the SoapExtensionPoint element to contain any XSD schema content. This would defeat the purpose
/// of having an extension point, since an extension point that allows any child content would pass validation.
///
/// Instead, we trick things out, and define this element as not having any children.
/// </summary>
/// <param name="xs"></param>
/// <returns>This class's schema</returns>
public static XmlSchemaComplexType GetSoapExtensionPointSchema(System.Xml.Schema.XmlSchemaSet xs)
{
XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));
XmlSchema schema = (XmlSchema)schemaSerializer.Deserialize(new XmlTextReader(anonymousSchemaNamespace, new System.IO.StringReader(anonymousSchema)));
xs.Add(schema);
XmlQualifiedName name = new XmlQualifiedName("SoapExtensionPointType", anonymousSchemaNamespace);
return (XmlSchemaComplexType)schema.SchemaTypes[name];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment