using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Channels; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; using SoapCore.ServiceModel; namespace SoapCore.Meta { internal class MetaWCFBodyWriter : BodyWriter { #pragma warning disable SA1310 // Field names must not contain underscore private const string XMLNS_XS = "http://www.w3.org/2001/XMLSchema"; private const string TRANSPORT_SCHEMA = "http://schemas.xmlsoap.org/soap/http"; private const string ARRAYS_NS = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"; private const string SYSTEM_NS = "http://schemas.datacontract.org/2004/07/System"; private const string DataContractNamespace = "http://schemas.datacontract.org/2004/07/"; private const string SERIALIZATION_NS = "http://schemas.microsoft.com/2003/10/Serialization/"; #pragma warning restore SA1310 // Field names must not contain underscore #pragma warning disable SA1009 // Closing parenthesis must be spaced correctly #pragma warning disable SA1008 // Opening parenthesis must be spaced correctly private static readonly Dictionary<string, (string, string)> SysTypeDic = new Dictionary<string, (string, string)>() { ["System.String"] = ("string", SYSTEM_NS), ["System.Boolean"] = ("boolean", SYSTEM_NS), ["System.Int16"] = ("short", SYSTEM_NS), ["System.Int32"] = ("int", SYSTEM_NS), ["System.Int64"] = ("long", SYSTEM_NS), ["System.Byte"] = ("byte", SYSTEM_NS), ["System.SByte"] = ("byte", SYSTEM_NS), ["System.UInt16"] = ("unsignedShort", SYSTEM_NS), ["System.UInt32"] = ("unsignedInt", SYSTEM_NS), ["System.UInt64"] = ("unsignedLong", SYSTEM_NS), ["System.Decimal"] = ("decimal", SYSTEM_NS), ["System.Double"] = ("double", SYSTEM_NS), ["System.Single"] = ("float", SYSTEM_NS), ["System.DateTime"] = ("dateTime", SYSTEM_NS), ["System.Guid"] = ("guid", SERIALIZATION_NS), ["System.Char"] = ("char", SERIALIZATION_NS), ["System.TimeSpan"] = ("duration", SERIALIZATION_NS), ["System.Object"] = ("anyType", SERIALIZATION_NS) }; #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly #pragma warning restore SA1009 // Closing parenthesis must be spaced correctly private static int _namespaceCounter = 1; private readonly ServiceDescription _service; private readonly string _baseUrl; private readonly Binding _binding; private readonly Dictionary<Type, string> _complexTypeToBuild = new Dictionary<Type, string>(); private readonly HashSet<Type> _complexTypeProcessed = new HashSet<Type>(); // Contains types that have been discovered private readonly Queue<Type> _arrayToBuild; private readonly HashSet<string> _builtEnumTypes; private readonly HashSet<string> _builtComplexTypes; private readonly HashSet<string> _buildArrayTypes; private readonly HashSet<string> _builtSerializationElements; private bool _buildDateTimeOffset; private bool _buildDataTable; private string _schemaNamespace; public MetaWCFBodyWriter(ServiceDescription service, string baseUrl, Binding binding) : base(isBuffered: true) { _service = service; _baseUrl = baseUrl; _binding = binding; _arrayToBuild = new Queue<Type>(); _builtEnumTypes = new HashSet<string>(); _builtComplexTypes = new HashSet<string>(); _buildArrayTypes = new HashSet<string>(); _builtSerializationElements = new HashSet<string>(); BindingType = service.Contracts.First().Name; if (binding != null) { BindingName = binding.Name; PortName = binding.Name; } else { BindingName = "BasicHttpBinding_" + _service.Contracts.First().Name; PortName = "BasicHttpBinding_" + _service.Contracts.First().Name; } } private string BindingName { get; } private string BindingType { get; } private string PortName { get; } private string TargetNameSpace => _service.Contracts.First().Namespace; protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { AddTypes(writer); AddMessage(writer); AddPortType(writer); AddBinding(writer); AddService(writer); } private static string GetModelNamespace(string @namespace) { if (@namespace.StartsWith("http")) { return @namespace; } return $"{DataContractNamespace}{@namespace}"; } private static string GetDataContractNamespace(Type type) { if (type.IsArray || typeof(IEnumerable).IsAssignableFrom(type)) { type = type.IsArray ? type.GetElementType() : GetGenericType(type); } var dataContractAttribute = type.GetCustomAttribute<DataContractAttribute>(); if (dataContractAttribute != null && !string.IsNullOrEmpty(dataContractAttribute.Namespace)) { return dataContractAttribute.Namespace; } return GetModelNamespace(type.Namespace); } private static Type GetGenericType(Type collectionType) { // Recursively look through the base class to find the Generic Type of the Enumerable var baseType = collectionType; var baseTypeInfo = collectionType.GetTypeInfo(); while (!baseTypeInfo.IsGenericType && baseTypeInfo.BaseType != null) { baseType = baseTypeInfo.BaseType; baseTypeInfo = baseType.GetTypeInfo(); } return baseType.GetTypeInfo().GetGenericArguments().DefaultIfEmpty(typeof(object)).FirstOrDefault(); } private string GetModelNamespace(Type type) { if (type != null && type.Namespace != _service.ServiceType.Namespace) { return $"{DataContractNamespace}{type.Namespace}"; } return $"{DataContractNamespace}{_service.ServiceType.Namespace}"; } private void WriteParameters(XmlDictionaryWriter writer, SoapMethodParameterInfo[] parameterInfos) { foreach (var parameterInfo in parameterInfos) { var elementAttribute = parameterInfo.Parameter.GetCustomAttribute<XmlElementAttribute>(); var parameterName = !string.IsNullOrEmpty(elementAttribute?.ElementName) ? elementAttribute.ElementName : parameterInfo.Parameter.GetCustomAttribute<MessageParameterAttribute>()?.Name ?? parameterInfo.Parameter.Name; AddSchemaType(writer, parameterInfo.Parameter.ParameterType, parameterName, objectNamespace: elementAttribute?.Namespace ?? (parameterInfo.Namespace != "http://tempuri.org/" ? parameterInfo.Namespace : null)); } } private void AddOperations(XmlDictionaryWriter writer) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", TargetNameSpace); writer.WriteAttributeString("xmlns:xs", XMLNS_XS); writer.WriteAttributeString("xmlns:ser", SERIALIZATION_NS); _schemaNamespace = TargetNameSpace; _namespaceCounter = 1; //discovery all parameters types which namespaceses diff with service namespace foreach (var operation in _service.Operations) { foreach (var parameter in operation.AllParameters) { var type = parameter.Parameter.ParameterType; var typeInfo = type.GetTypeInfo(); if (typeInfo.IsByRef) { type = typeInfo.GetElementType(); } if (TypeIsComplexForWsdl(type, out type)) { _complexTypeToBuild[type] = GetDataContractNamespace(type); DiscoveryTypesByProperties(type, true); } else if (type.IsEnum || Nullable.GetUnderlyingType(type)?.IsEnum == true) { _complexTypeToBuild[type] = GetDataContractNamespace(type); DiscoveryTypesByProperties(type, true); } } if (operation.DispatchMethod.ReturnType != typeof(void) && operation.DispatchMethod.ReturnType != typeof(Task)) { var returnType = operation.DispatchMethod.ReturnType; if (returnType.IsConstructedGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) { returnType = returnType.GetGenericArguments().First(); } if (TypeIsComplexForWsdl(returnType, out returnType)) { _complexTypeToBuild[returnType] = GetDataContractNamespace(returnType); DiscoveryTypesByProperties(returnType, true); } else if (returnType.IsEnum || Nullable.GetUnderlyingType(returnType)?.IsEnum == true) { _complexTypeToBuild[returnType] = GetDataContractNamespace(returnType); DiscoveryTypesByProperties(returnType, true); } } } var groupedByNamespace = _complexTypeToBuild.GroupBy(x => x.Value).ToDictionary(x => x.Key, x => x.Select(k => k.Key)); foreach (var @namespace in groupedByNamespace.Keys.Where(x => x != null && x != _service.ServiceType.Namespace).Distinct()) { writer.WriteStartElement("xs:import"); writer.WriteAttributeString("namespace", @namespace); writer.WriteEndElement(); } foreach (var operation in _service.Operations) { // input parameters of operation writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", operation.Name); writer.WriteStartElement("xs:complexType"); writer.WriteStartElement("xs:sequence"); WriteParameters(writer, operation.InParameters); writer.WriteEndElement(); // xs:sequence writer.WriteEndElement(); // xs:complexType writer.WriteEndElement(); // xs:element // output parameter / return of operation writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", operation.Name + "Response"); writer.WriteStartElement("xs:complexType"); writer.WriteStartElement("xs:sequence"); if (operation.DispatchMethod.ReturnType != typeof(void) && operation.DispatchMethod.ReturnType != typeof(Task)) { var returnType = operation.DispatchMethod.ReturnType; if (returnType.IsConstructedGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) { returnType = returnType.GetGenericArguments().First(); } var returnName = operation.DispatchMethod.ReturnParameter.GetCustomAttribute<MessageParameterAttribute>()?.Name ?? operation.Name + "Result"; AddSchemaType(writer, returnType, returnName, false, GetDataContractNamespace(returnType)); } WriteParameters(writer, operation.OutParameters); writer.WriteEndElement(); // xs:sequence writer.WriteEndElement(); // xs:complexType writer.WriteEndElement(); // xs:element AddFaultTypes(writer, operation); } writer.WriteEndElement(); // xs:schema } private void AddFaultTypes(XmlDictionaryWriter writer, OperationDescription operation) { foreach (var faultType in operation.Faults) { if (_complexTypeProcessed.Contains(faultType)) { continue; } _complexTypeToBuild[faultType] = GetDataContractNamespace(faultType); DiscoveryTypesByProperties(faultType, true); } } private void AddTypes(XmlDictionaryWriter writer) { writer.WriteStartElement("wsdl:types"); AddOperations(writer); AddMSSerialization(writer); AddComplexTypes(writer); AddArrayTypes(writer); AddSystemTypes(writer); writer.WriteEndElement(); // wsdl:types } private void AddSystemTypes(XmlDictionaryWriter writer) { if (_buildDateTimeOffset) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("xmlns:xs", XMLNS_XS); writer.WriteAttributeString("xmlns:tns", SYSTEM_NS); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", SYSTEM_NS); writer.WriteStartElement("xs:import"); writer.WriteAttributeString("namespace", SERIALIZATION_NS); writer.WriteEndElement(); writer.WriteStartElement("xs:complexType"); writer.WriteAttributeString("name", "DateTimeOffset"); writer.WriteStartElement("xs:annotation"); writer.WriteStartElement("xs:appinfo"); writer.WriteElementString("IsValueType", SERIALIZATION_NS, "true"); writer.WriteEndElement(); // xs:appinfo writer.WriteEndElement(); // xs:annotation writer.WriteStartElement("xs:sequence"); writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", "DateTime"); writer.WriteAttributeString("type", "xs:dateTime"); writer.WriteEndElement(); writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", "OffsetMinutes"); writer.WriteAttributeString("type", "xs:short"); writer.WriteEndElement(); writer.WriteEndElement(); // xs:sequence writer.WriteEndElement(); // xs:complexType writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", "DateTimeOffset"); writer.WriteAttributeString("nillable", "true"); writer.WriteAttributeString("type", "tns:DateTimeOffset"); writer.WriteEndElement(); writer.WriteEndElement(); // xs:schema } if (_buildDataTable) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", "http://schemas.datacontract.org/2004/07/System.Data"); writer.WriteAttributeString("xmlns:xs", "http://www.w3.org/2001/XMLSchema"); writer.WriteAttributeString("xmlns:tns", "http://schemas.datacontract.org/2004/07/System.Data"); writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", "DataTable"); writer.WriteAttributeString("nillable", "true"); writer.WriteStartElement("xs:complexType"); writer.WriteStartElement("xs:annotation"); writer.WriteStartElement("xs:appinfo"); writer.WriteStartElement("ActualType"); writer.WriteAttributeString("xmlns", "http://schemas.microsoft.com/2003/10/Serialization/"); writer.WriteAttributeString("Name", "DataTable"); writer.WriteAttributeString("Namespace", "http://schemas.datacontract.org/2004/07/System.Data"); writer.WriteEndElement(); //actual type writer.WriteEndElement(); //appinfo writer.WriteEndElement(); //annotation writer.WriteStartElement("xs:sequence"); writer.WriteStartElement("xs:any"); writer.WriteAttributeString("minOccurs", "0"); writer.WriteAttributeString("maxOccurs", "unbounded"); writer.WriteAttributeString("namespace", "http://www.w3.org/2001/XMLSchema"); writer.WriteAttributeString("processContents", "lax"); writer.WriteEndElement(); //any writer.WriteStartElement("xs:any"); writer.WriteAttributeString("minOccurs", "1"); writer.WriteAttributeString("namespace", "urn:schemas-microsoft-com:xml-diffgram-v1"); writer.WriteAttributeString("processContents", "lax"); writer.WriteEndElement(); //any writer.WriteEndElement(); //sequence writer.WriteEndElement(); //complexType writer.WriteEndElement(); //element writer.WriteEndElement(); //schema } } private void AddArrayTypes(XmlDictionaryWriter writer) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("xmlns:xs", XMLNS_XS); writer.WriteAttributeString("xmlns:tns", ARRAYS_NS); writer.WriteAttributeString("xmlns:ser", SERIALIZATION_NS); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", ARRAYS_NS); _namespaceCounter = 1; _schemaNamespace = ARRAYS_NS; writer.WriteStartElement("xs:import"); writer.WriteAttributeString("namespace", SERIALIZATION_NS); writer.WriteEndElement(); while (_arrayToBuild.Count > 0) { var toBuild = _arrayToBuild.Dequeue(); var elType = toBuild.IsArray ? toBuild.GetElementType() : GetGenericType(toBuild); var sysType = ResolveSystemType(elType); var toBuildName = "ArrayOf" + sysType.name; if (!_buildArrayTypes.Contains(toBuildName)) { writer.WriteStartElement("xs:complexType"); writer.WriteAttributeString("name", toBuildName); writer.WriteStartElement("xs:sequence"); AddSchemaType(writer, elType, null, true); writer.WriteEndElement(); // :sequence writer.WriteEndElement(); // xs:complexType writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", toBuildName); writer.WriteAttributeString("nillable", "true"); writer.WriteAttributeString("type", "tns:" + toBuildName); writer.WriteEndElement(); // xs:element _buildArrayTypes.Add(toBuildName); } } writer.WriteEndElement(); // xs:schema } private void AddMSSerialization(XmlDictionaryWriter writer) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("attributeFormDefault", "qualified"); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", SERIALIZATION_NS); writer.WriteAttributeString("xmlns:xs", XMLNS_XS); writer.WriteAttributeString("xmlns:tns", SERIALIZATION_NS); WriteSerializationElement(writer, "anyType", "xs:anyType", true); WriteSerializationElement(writer, "anyURI", "xs:anyURI", true); WriteSerializationElement(writer, "base64Binary", "xs:base64Binary", true); WriteSerializationElement(writer, "boolean", "xs:boolean", true); WriteSerializationElement(writer, "byte", "xs:byte", true); WriteSerializationElement(writer, "dateTime", "xs:dateTime", true); WriteSerializationElement(writer, "decimal", "xs:decimal", true); WriteSerializationElement(writer, "double", "xs:double", true); WriteSerializationElement(writer, "float", "xs:float", true); WriteSerializationElement(writer, "int", "xs:int", true); WriteSerializationElement(writer, "long", "xs:long", true); WriteSerializationElement(writer, "QName", "xs:QName", true); WriteSerializationElement(writer, "short", "xs:short", true); WriteSerializationElement(writer, "string", "xs:string", true); WriteSerializationElement(writer, "unsignedByte", "xs:unsignedByte", true); WriteSerializationElement(writer, "unsignedInt", "xs:unsignedInt", true); WriteSerializationElement(writer, "unsignedLong", "xs:unsignedLong", true); WriteSerializationElement(writer, "unsignedShort", "xs:unsignedShort", true); WriteSerializationElement(writer, "char", "tns:char", true); writer.WriteStartElement("xs:simpleType"); writer.WriteAttributeString("name", "char"); writer.WriteStartElement("xs:restriction"); writer.WriteAttributeString("base", "xs:int"); writer.WriteEndElement(); writer.WriteEndElement(); WriteSerializationElement(writer, "duration", "tns:duration", true); writer.WriteStartElement("xs:simpleType"); writer.WriteAttributeString("name", "duration"); writer.WriteStartElement("xs:restriction"); writer.WriteAttributeString("base", "xs:duration"); writer.WriteStartElement("xs:pattern"); writer.WriteAttributeString("value", @"\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?"); writer.WriteEndElement(); writer.WriteStartElement("xs:minInclusive"); writer.WriteAttributeString("value", @"-P10675199DT2H48M5.4775808S"); writer.WriteEndElement(); writer.WriteStartElement("xs:maxInclusive"); writer.WriteAttributeString("value", @"P10675199DT2H48M5.4775807S"); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); WriteSerializationElement(writer, "guid", "tns:guid", true); writer.WriteStartElement("xs:simpleType"); writer.WriteAttributeString("name", "guid"); writer.WriteStartElement("xs:restriction"); writer.WriteAttributeString("base", "xs:string"); writer.WriteStartElement("xs:pattern"); writer.WriteAttributeString("value", @"[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}"); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteStartElement("xs:attribute"); writer.WriteAttributeString("name", "FactoryType"); writer.WriteAttributeString("type", "xs:QName"); writer.WriteEndElement(); writer.WriteStartElement("xs:attribute"); writer.WriteAttributeString("name", "Id"); writer.WriteAttributeString("type", "xs:ID"); writer.WriteEndElement(); writer.WriteStartElement("xs:attribute"); writer.WriteAttributeString("name", "Ref"); writer.WriteAttributeString("type", "xs:IDREF"); writer.WriteEndElement(); writer.WriteEndElement(); //schema } private void WriteSerializationElement(XmlDictionaryWriter writer, string name, string type, bool nillable) { if (!_builtSerializationElements.Contains(name)) { writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", name); writer.WriteAttributeString("nillable", nillable ? "true" : "false"); writer.WriteAttributeString("type", type); writer.WriteEndElement(); _builtSerializationElements.Add(name); } } private void AddComplexTypes(XmlDictionaryWriter writer) { foreach (var type in _complexTypeToBuild.ToArray()) { _complexTypeToBuild[type.Key] = GetDataContractNamespace(type.Key); DiscoveryTypesByProperties(type.Key, true); } var groupedByNamespace = _complexTypeToBuild.GroupBy(x => x.Value).ToDictionary(x => x.Key, x => x.Select(k => k.Key)); foreach (var types in groupedByNamespace.Distinct()) { writer.WriteStartElement("xs:schema"); writer.WriteAttributeString("elementFormDefault", "qualified"); writer.WriteAttributeString("targetNamespace", GetModelNamespace(types.Key)); writer.WriteAttributeString("xmlns:xs", XMLNS_XS); writer.WriteAttributeString("xmlns:tns", GetModelNamespace(types.Key)); writer.WriteAttributeString("xmlns:ser", SERIALIZATION_NS); _namespaceCounter = 1; _schemaNamespace = GetModelNamespace(types.Key); writer.WriteStartElement("xs:import"); writer.WriteAttributeString("namespace", SYSTEM_NS); writer.WriteEndElement(); writer.WriteStartElement("xs:import"); writer.WriteAttributeString("namespace", ARRAYS_NS); writer.WriteEndElement(); foreach (var type in types.Value.Distinct(new TypesComparer(GetTypeName))) { if (type.IsEnum) { WriteEnum(writer, type); } else { WriteComplexType(writer, type); } writer.WriteStartElement("xs:element"); writer.WriteAttributeString("name", GetTypeName(type)); if (!type.IsEnum || Nullable.GetUnderlyingType(type) != null) { writer.WriteAttributeString("nillable", "true"); } writer.WriteAttributeString("type", "tns:" + GetTypeName(type)); writer.WriteEndElement(); // xs:element } writer.WriteEndElement(); } } private void DiscoveryTypesByProperties(Type type, bool isRootType) { //guard against infinity recursion //check is made against _complexTypeProcessed, which contains types that have been //discovered by the current method if (_complexTypeProcessed.Contains(type)) { return; } if (type == typeof(DateTimeOffset)) { return; } //type will be processed, so can be added to _complexTypeProcessed _complexTypeProcessed.Add(type); if (HasBaseType(type) && type.BaseType != null) { _complexTypeToBuild[type.BaseType] = GetDataContractNamespace(type.BaseType); DiscoveryTypesByProperties(type.BaseType, false); } foreach (var property in type.GetProperties().Where(prop => prop.DeclaringType == type && prop.CustomAttributes.All(attr => attr.AttributeType.Name != "IgnoreDataMemberAttribute") && !prop.PropertyType.IsPrimitive && !SysTypeDic.ContainsKey(prop.PropertyType.FullName) && prop.PropertyType != typeof(ValueType) && prop.PropertyType != typeof(DateTimeOffset))) { Type propertyType; var underlyingType = Nullable.GetUnderlyingType(property.PropertyType); if (Nullable.GetUnderlyingType(property.PropertyType) != null) { propertyType = underlyingType; } else if (property.PropertyType.IsArray || typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) { propertyType = property.PropertyType.IsArray ? property.PropertyType.GetElementType() : GetGenericType(property.PropertyType); _complexTypeToBuild[property.PropertyType] = GetDataContractNamespace(property.PropertyType); } else { propertyType = property.PropertyType; } if (propertyType != null && !propertyType.IsPrimitive && !SysTypeDic.ContainsKey(propertyType.FullName)) { if (propertyType == type) { continue; } _complexTypeToBuild[propertyType] = GetDataContractNamespace(propertyType); DiscoveryTypesByProperties(propertyType, false); } } } private void WriteEnum(XmlDictionaryWriter writer, Type type) { if (type.IsByRef) { type = type.GetElementType(); } var typeName = GetTypeName(type); if (!_builtEnumTypes.Contains(typeName)) { writer.WriteStartElement("xs:simpleType"); writer.WriteAttributeString("name", typeName); writer.WriteStartElement("xs:restriction "); writer.WriteAttributeString("base", "xs:string"); foreach (var name in Enum.GetNames(type)) { writer.WriteStartElement("xs:enumeration "); // Search for EnumMember attribute. If available, get enum value from its Value field var enumMemberAttribute = ((EnumMemberAttribute[])type.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).SingleOrDefault(); var value = enumMemberAttribute is null || !enumMemberAttribute.IsValueSetExplicitly ? name : enumMemberAttribute.Value; writer.WriteAttributeString("value", value); writer.WriteEndElement(); // xs:enumeration } writer.WriteEndElement(); // xs:restriction writer.WriteEndElement(); // xs:simpleType _builtEnumTypes.Add(typeName); } } private void WriteComplexType(XmlDictionaryWriter writer, Type type) { var toBuildName = GetTypeName(type); if (_builtComplexTypes.Contains(toBuildName)) { return; } writer.WriteStartElement("xs:complexType"); writer.WriteAttributeString("name", toBuildName); writer.WriteAttributeString("xmlns:ser", SERIALIZATION_NS); if (type.IsValueType && ResolveSystemType(type).name == null) { writer.WriteStartElement("xs:annotation"); writer.WriteStartElement("xs:appinfo"); writer.WriteStartElement("IsValueType"); writer.WriteAttributeString("xmlns", SERIALIZATION_NS); writer.WriteValue(true); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); } var hasBaseType = HasBaseType(type); if (hasBaseType) { writer.WriteStartElement("xs:complexContent"); writer.WriteAttributeString("mixed", "false"); writer.WriteStartElement("xs:extension"); var modelNamespace = GetDataContractNamespace(type.BaseType); var typeName = type.BaseType.Name; if (_schemaNamespace != modelNamespace) { var ns = $"q{_namespaceCounter++}"; writer.WriteAttributeString("base", $"{ns}:{typeName}"); writer.WriteAttributeString($"xmlns:{ns}", modelNamespace); } else { writer.WriteAttributeString("base", $"tns:{typeName}"); } } writer.WriteStartElement("xs:sequence"); if (type.IsArray || typeof(IEnumerable).IsAssignableFrom(type)) { var elementType = type.IsArray ? type.GetElementType() : GetGenericType(type); AddSchemaType(writer, elementType, null, true, GetDataContractNamespace(type)); } else { var properties = type.GetProperties().Where(prop => prop.DeclaringType == type && prop.CustomAttributes.All(attr => attr.AttributeType.Name != "IgnoreDataMemberAttribute")); var dataMembersToWrite = new List<DataMemberDescription>(); //TODO: base type properties //TODO: enforce order attribute parameters foreach (var property in properties) { var propertyName = property.Name; var attributes = property.GetCustomAttributes(true); int order = 0; foreach (var attr in attributes) { if (attr is DataMemberAttribute dataContractAttribute) { if (!string.IsNullOrEmpty(dataContractAttribute.Name)) { propertyName = dataContractAttribute.Name; } if (dataContractAttribute.Order > 0) { order = dataContractAttribute.Order; } break; } } dataMembersToWrite.Add(new DataMemberDescription { Name = propertyName, Type = property.PropertyType, Order = order }); } foreach (var p in dataMembersToWrite.OrderBy(x => x.Order).ThenBy(p => p.Name, StringComparer.Ordinal)) { AddSchemaType(writer, p.Type, p.Name, false, GetDataContractNamespace(p.Type)); } } writer.WriteEndElement(); // xs:sequence if (hasBaseType) { writer.WriteEndElement(); // xs:extension writer.WriteEndElement(); // xs:complexContent } writer.WriteEndElement(); // xs:complexType _builtComplexTypes.Add(toBuildName); } private void AddMessage(XmlDictionaryWriter writer) { foreach (var operation in _service.Operations) { // input writer.WriteStartElement("wsdl:message"); writer.WriteAttributeString("name", $"{BindingType}_{operation.Name}_InputMessage"); writer.WriteStartElement("wsdl:part"); writer.WriteAttributeString("name", "parameters"); writer.WriteAttributeString("element", "tns:" + operation.Name); writer.WriteEndElement(); // wsdl:part writer.WriteEndElement(); // wsdl:message // output writer.WriteStartElement("wsdl:message"); writer.WriteAttributeString("name", $"{BindingType}_{operation.Name}_OutputMessage"); writer.WriteStartElement("wsdl:part"); writer.WriteAttributeString("name", "parameters"); writer.WriteAttributeString("element", "tns:" + operation.Name + "Response"); writer.WriteEndElement(); // wsdl:part writer.WriteEndElement(); // wsdl:message AddMessageFaults(writer, operation); } } private void AddMessageFaults(XmlDictionaryWriter writer, OperationDescription operation) { foreach (Type fault in operation.Faults) { writer.WriteStartElement("wsdl:message"); writer.WriteAttributeString("name", $"{BindingType}_{operation.Name}_{fault.Name}Fault_FaultMessage"); writer.WriteStartElement("wsdl:part"); writer.WriteAttributeString("name", "detail"); var ns = $"q{_namespaceCounter++}"; writer.WriteAttributeString("element", $"{ns}:{fault.Name}"); writer.WriteAttributeString($"xmlns:{ns}", GetDataContractNamespace(fault)); writer.WriteEndElement(); // wsdl:part writer.WriteEndElement(); // wsdl:message } } private void AddPortType(XmlDictionaryWriter writer) { writer.WriteStartElement("wsdl:portType"); writer.WriteAttributeString("name", BindingType); foreach (var operation in _service.Operations) { writer.WriteStartElement("wsdl:operation"); writer.WriteAttributeString("name", operation.Name); writer.WriteStartElement("wsdl:input"); writer.WriteAttributeString("wsam:Action", operation.SoapAction); writer.WriteAttributeString("message", $"tns:{BindingType}_{operation.Name}_InputMessage"); writer.WriteEndElement(); // wsdl:input writer.WriteStartElement("wsdl:output"); writer.WriteAttributeString("wsam:Action", operation.SoapAction + "Response"); writer.WriteAttributeString("message", $"tns:{BindingType}_{operation.Name}_OutputMessage"); writer.WriteEndElement(); // wsdl:output AddPortTypeFaults(writer, operation); writer.WriteEndElement(); // wsdl:operation } writer.WriteEndElement(); // wsdl:portType } private void AddPortTypeFaults(XmlDictionaryWriter writer, OperationDescription operation) { foreach (Type fault in operation.Faults) { writer.WriteStartElement("wsdl:fault"); writer.WriteAttributeString("wsam:Action", $"{operation.SoapAction}{fault.Name}Fault"); writer.WriteAttributeString("name", $"{fault.Name}Fault"); writer.WriteAttributeString("message", $"tns:{BindingType}_{operation.Name}_{fault.Name}Fault_FaultMessage"); writer.WriteEndElement(); // wsdl:fault } } private void AddBinding(XmlDictionaryWriter writer) { writer.WriteStartElement("wsdl:binding"); writer.WriteAttributeString("name", BindingName); writer.WriteAttributeString("type", "tns:" + BindingType); if (_binding.HasBasicAuth()) { writer.WriteStartElement("wsp:PolicyReference"); writer.WriteAttributeString("URI", $"#{_binding.Name}_{_service.Contracts.First().Name}_policy"); writer.WriteEndElement(); } writer.WriteStartElement("soap:binding"); writer.WriteAttributeString("transport", TRANSPORT_SCHEMA); writer.WriteEndElement(); // soap:binding foreach (var operation in _service.Operations) { writer.WriteStartElement("wsdl:operation"); writer.WriteAttributeString("name", operation.Name); writer.WriteStartElement("soap:operation"); writer.WriteAttributeString("soapAction", operation.SoapAction); writer.WriteAttributeString("style", "document"); writer.WriteEndElement(); // soap:operation writer.WriteStartElement("wsdl:input"); writer.WriteStartElement("soap:body"); writer.WriteAttributeString("use", "literal"); writer.WriteEndElement(); // soap:body writer.WriteEndElement(); // wsdl:input writer.WriteStartElement("wsdl:output"); writer.WriteStartElement("soap:body"); writer.WriteAttributeString("use", "literal"); writer.WriteEndElement(); // soap:body writer.WriteEndElement(); // wsdl:output AddBindingFaults(writer, operation); writer.WriteEndElement(); // wsdl:operation } writer.WriteEndElement(); // wsdl:binding } private void AddBindingFaults(XmlDictionaryWriter writer, OperationDescription operation) { foreach (Type fault in operation.Faults) { writer.WriteStartElement("wsdl:fault"); writer.WriteAttributeString("name", $"{fault.Name}Fault"); writer.WriteStartElement("soap:fault"); writer.WriteAttributeString("use", "literal"); writer.WriteAttributeString("name", $"{fault.Name}Fault"); writer.WriteEndElement(); // soap:fault writer.WriteEndElement(); // wsdl:fault } } private void AddService(XmlDictionaryWriter writer) { writer.WriteStartElement("wsdl:service"); writer.WriteAttributeString("name", _service.ServiceType.Name); writer.WriteStartElement("wsdl:port"); writer.WriteAttributeString("name", PortName); writer.WriteAttributeString("binding", "tns:" + BindingName); writer.WriteStartElement("soap:address"); writer.WriteAttributeString("location", _baseUrl); writer.WriteEndElement(); // soap:address writer.WriteEndElement(); // wsdl:port } private void AddSchemaType(XmlDictionaryWriter writer, Type type, string name, bool isArray = false, string objectNamespace = null) { var typeInfo = type.GetTypeInfo(); var typeName = GetTypeName(type); if (typeInfo.IsByRef) { type = typeInfo.GetElementType(); } writer.WriteStartElement("xs:element"); if (objectNamespace == null) { objectNamespace = GetModelNamespace(type); } if (typeInfo.IsEnum || Nullable.GetUnderlyingType(typeInfo)?.IsEnum == true) { WriteComplexElementType(writer, typeName, _schemaNamespace, objectNamespace, type); if (string.IsNullOrEmpty(name)) { name = typeName; } writer.WriteAttributeString("name", name); } else if (type.IsValueType) { string xsTypename; if (typeof(DateTimeOffset).IsAssignableFrom(type)) { if (string.IsNullOrEmpty(name)) { name = typeName; } var ns = $"q{_namespaceCounter++}"; xsTypename = $"{ns}:{typeName}"; writer.WriteAttributeString($"xmlns:{ns}", SYSTEM_NS); _buildDateTimeOffset = true; } else { var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { var sysType = ResolveSystemType(underlyingType); xsTypename = $"{(sysType.ns == SERIALIZATION_NS ? "ser" : "xs")}:{sysType.name}"; writer.WriteAttributeString("nillable", "true"); } else if (ResolveSystemType(type).name != null) { var sysType = ResolveSystemType(type); xsTypename = $"{(sysType.ns == SERIALIZATION_NS ? "ser" : "xs")}:{sysType.name}"; } else { var ns = $"q{_namespaceCounter++}"; xsTypename = $"{ns}:{typeName}"; } } writer.WriteAttributeString("minOccurs", "0"); if (isArray) { writer.WriteAttributeString("maxOccurs", "unbounded"); } if (string.IsNullOrEmpty(name)) { name = xsTypename.Split(':')[1]; } writer.WriteAttributeString("name", name); writer.WriteAttributeString("type", xsTypename); } else { writer.WriteAttributeString("minOccurs", "0"); if (isArray) { writer.WriteAttributeString("maxOccurs", "unbounded"); } if (type.Name == "String" || type.Name == "String&") { if (string.IsNullOrEmpty(name)) { name = "string"; } writer.WriteAttributeString("name", name); writer.WriteAttributeString("nillable", "true"); writer.WriteAttributeString("type", "xs:string"); } else if (type.Name == "Object" || type.Name == "Object&") { writer.WriteAttributeString("name", "anyType"); writer.WriteAttributeString("type", "xs:anyType"); } else if (type == typeof(System.Xml.Linq.XElement)) { writer.WriteAttributeString("name", name); writer.WriteAttributeString("nillable", "true"); writer.WriteStartElement("xs:complexType"); writer.WriteStartElement("xs:sequence"); writer.WriteStartElement("xs:any"); writer.WriteAttributeString("minOccurs", "0"); writer.WriteAttributeString("processContents", "lax"); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); } else if (type == typeof(DataTable)) { _buildDataTable = true; writer.WriteAttributeString("name", name); writer.WriteAttributeString("nillable", "true"); writer.WriteStartElement("xs:complexType"); writer.WriteStartElement("xs:annotation"); writer.WriteStartElement("xs:appinfo"); writer.WriteStartElement("ActualType"); writer.WriteAttributeString("xmlns", "http://schemas.microsoft.com/2003/10/Serialization/"); writer.WriteAttributeString("Name", "DataTable"); writer.WriteAttributeString("Namespace", "http://schemas.datacontract.org/2004/07/System.Data"); writer.WriteEndElement(); //actual type writer.WriteEndElement(); // appinfo writer.WriteEndElement(); //annotation writer.WriteEndElement(); //complex type writer.WriteStartElement("xs:sequence"); writer.WriteStartElement("xs:any"); writer.WriteAttributeString("minOccurs", "0"); writer.WriteAttributeString("maxOccurs", "unbounded"); writer.WriteAttributeString("namespace", "http://www.w3.org/2001/XMLSchema"); writer.WriteAttributeString("processContents", "lax"); writer.WriteEndElement(); writer.WriteStartElement("xs:any"); writer.WriteAttributeString("minOccurs", "1"); writer.WriteAttributeString("namespace", "urn:schemas-microsoft-com:xml-diffgram-v1"); writer.WriteAttributeString("processContents", "lax"); writer.WriteEndElement(); writer.WriteEndElement(); //sequence } else if (type.Name == "Byte[]") { if (string.IsNullOrEmpty(name)) { name = "base64Binary"; } writer.WriteAttributeString("name", name); writer.WriteAttributeString("type", "xs:base64Binary"); } else if (type == typeof(Stream) || typeof(Stream).IsAssignableFrom(type)) { name = "StreamBody"; writer.WriteAttributeString("name", name); writer.WriteAttributeString("type", "xs:base64Binary"); } else if (typeof(IEnumerable).IsAssignableFrom(type)) { var elType = type.IsArray ? type.GetElementType() : GetGenericType(type); var sysType = ResolveSystemType(elType); if (sysType.name != null) { if (string.IsNullOrEmpty(name)) { name = typeName; } var ns = $"q{_namespaceCounter++}"; writer.WriteAttributeString($"xmlns:{ns}", ARRAYS_NS); writer.WriteAttributeString("name", name); writer.WriteAttributeString("nillable", "true"); writer.WriteAttributeString("type", $"{ns}:ArrayOf{sysType.name}"); _arrayToBuild.Enqueue(type); } else { if (string.IsNullOrEmpty(name)) { name = typeName; } writer.WriteAttributeString("name", name); WriteComplexElementType(writer, typeName, _schemaNamespace, objectNamespace, type); _complexTypeToBuild[type] = GetDataContractNamespace(type); } } else { if (string.IsNullOrEmpty(name)) { name = typeName; } writer.WriteAttributeString("name", name); WriteComplexElementType(writer, typeName, _schemaNamespace, objectNamespace, type); _complexTypeToBuild[type] = GetDataContractNamespace(type); } } writer.WriteEndElement(); // xs:element } private bool TypeIsComplexForWsdl(Type type, out Type resultType) { var typeInfo = type.GetTypeInfo(); resultType = null; resultType = type; if (typeInfo.IsByRef) { type = typeInfo.GetElementType(); } if (typeof(IEnumerable).IsAssignableFrom(type)) { resultType = type.IsArray ? type.GetElementType() : GetGenericType(type); type = resultType; } if (typeInfo.IsEnum || typeInfo.IsValueType) { return false; } if (type.Name == "String" || type.Name == "String&") { return false; } if (type == typeof(System.Xml.Linq.XElement)) { return false; } if (type == typeof(DataTable)) { return false; } if (type.Name == "Byte[]") { return false; } if (SysTypeDic.ContainsKey(type.FullName)) { return false; } return true; } private void WriteComplexElementType(XmlDictionaryWriter writer, string typeName, string schemaNamespace, string objectNamespace, Type type) { var underlying = Nullable.GetUnderlyingType(type); if (!type.IsEnum || underlying != null) { writer.WriteAttributeString("nillable", "true"); } // In case of Nullable<T>, type is replaced by the underlying type if (underlying?.IsEnum == true) { type = underlying; typeName = GetTypeName(underlying); objectNamespace = GetModelNamespace(underlying); } if (schemaNamespace != objectNamespace) { var ns = $"q{_namespaceCounter++}"; writer.WriteAttributeString("type", $"{ns}:{typeName}"); writer.WriteAttributeString($"xmlns:{ns}", GetDataContractNamespace(type)); } else { writer.WriteAttributeString("type", $"tns:{typeName}"); } } private string GetTypeName(Type type) { if (type.IsGenericType && !type.IsArray && !typeof(IEnumerable).IsAssignableFrom(type)) { var genericType = GetGenericType(type); var genericTypeName = GetTypeName(genericType); var typeName = type.Name.Replace("`1", string.Empty); typeName = typeName + "Of" + genericTypeName; return typeName; } if (type.IsArray) { return "ArrayOf" + GetTypeName(type.GetElementType()); } if (typeof(IEnumerable).IsAssignableFrom(type)) { return "ArrayOf" + GetTypeName(GetGenericType(type)); } // Make use of DataContract attribute, if set, as it may contain a Name override var dataContractAttribute = type.GetCustomAttribute<DataContractAttribute>(); if (dataContractAttribute != null && !string.IsNullOrEmpty(dataContractAttribute.Name)) { return dataContractAttribute.Name; } return type.Name; } #pragma warning disable SA1009 // Closing parenthesis must be spaced correctly #pragma warning disable SA1008 // Opening parenthesis must be spaced correctly private (string name, string ns) ResolveSystemType(Type type) { type = type.IsEnum ? type.GetEnumUnderlyingType() : type; if (SysTypeDic.ContainsKey(type.FullName)) { return SysTypeDic[type.FullName]; } return (null, null); } #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly #pragma warning restore SA1009 // Closing parenthesis must be spaced correctly private bool HasBaseType(Type type) { var isArrayType = type.IsArray || typeof(IEnumerable).IsAssignableFrom(type); var baseType = type.GetTypeInfo().BaseType; return !isArrayType && !type.IsEnum && !type.IsPrimitive && !type.IsValueType && baseType != null && !baseType.Name.Equals("Object"); } } }