-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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