From 20d59c8dc285e3bfb28ee64ed6725e3444c5cf49 Mon Sep 17 00:00:00 2001 From: psalmnoelff Date: Thu, 5 Feb 2026 22:58:39 +0800 Subject: [PATCH 1/3] ArrayOfGuid and guid types in wsdl Adjusted ClrTypeResolver to return guid on "Guid" types but preserve "string" type by default MetaBodyWriter - use proper XML qualified namespace SoapEndpointMiddleware - make the UseMicrosoftGuid static for other method access --- src/SoapCore/Meta/ClrTypeResolver.cs | 13 ++++++++++++- src/SoapCore/Meta/MetaBodyWriter.cs | 7 +++++++ src/SoapCore/SoapEndpointMiddleware.cs | 3 +++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/SoapCore/Meta/ClrTypeResolver.cs b/src/SoapCore/Meta/ClrTypeResolver.cs index bb2a574a..fe1bff50 100644 --- a/src/SoapCore/Meta/ClrTypeResolver.cs +++ b/src/SoapCore/Meta/ClrTypeResolver.cs @@ -1,7 +1,16 @@ +using System; + namespace SoapCore.Meta { public class ClrTypeResolver { + /// + /// When true, Guid types are resolved as "guid" (for Microsoft WSDL types namespace) + /// instead of "string". This affects array naming (List<Guid> becomes ArrayOfGuid). + /// + [ThreadStatic] + public static bool UseMicrosoftGuid; + public static string ResolveOrDefault(string typeName) { switch (typeName) @@ -33,7 +42,9 @@ public static string ResolveOrDefault(string typeName) case "DateTime": return "dateTime"; case "Guid": - return "string"; + // When UseMicrosoftGuid is true, return null so callers fall back to original type name "Guid" + // This ensures ArrayOfGuid naming while avoiding invalid "s:guid" type references + return UseMicrosoftGuid ? null : "string"; case "Char": return "string"; case "TimeSpan": diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index caa980b1..bb1b4e8c 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -215,6 +215,13 @@ private static bool TryGetMessageContractBodyMemberInfo(Type type, out MemberInf private XmlQualifiedName ResolveType(Type type) { string typeName = type.IsEnum ? type.GetEnumUnderlyingType().Name : type.Name; + + // Handle Guid specially when UseMicrosoftGuid is enabled + if (typeName == "Guid" && _buildMicrosoftGuid) + { + return new XmlQualifiedName("guid", Namespaces.MICROSOFT_TYPES); + } + string resolvedType = ClrTypeResolver.ResolveOrDefault(typeName); if (string.IsNullOrEmpty(resolvedType)) diff --git a/src/SoapCore/SoapEndpointMiddleware.cs b/src/SoapCore/SoapEndpointMiddleware.cs index 69f98e7b..31cb407f 100644 --- a/src/SoapCore/SoapEndpointMiddleware.cs +++ b/src/SoapCore/SoapEndpointMiddleware.cs @@ -234,6 +234,9 @@ private async Task ReadMessageAsync(HttpContext httpContext, SoapMessag private async Task ProcessMeta(HttpContext httpContext, bool showDocumentation) { + // Set the flag for ClrTypeResolver so that List becomes ArrayOfGuid + Meta.ClrTypeResolver.UseMicrosoftGuid = _options.UseMicrosoftGuid; + var scheme = string.IsNullOrEmpty(_options.SchemeOverride) ? httpContext.Request.Scheme : _options.SchemeOverride; var baseUrl = scheme + "://" + httpContext.Request.Host + httpContext.Request.PathBase + httpContext.Request.Path; var xmlNamespaceLookup = GetXmlNamespaceLookup(null); From d4473a3ae7ca32efc588c81334ce96c6d0ce734e Mon Sep 17 00:00:00 2001 From: psalmnoelff Date: Fri, 6 Feb 2026 13:52:44 +0800 Subject: [PATCH 2/3] Add XmlIgnoreOnlyForWsdl option to skip IgnoreDataMember in WSDL generation --- src/SoapCore/Meta/BodyWriterExtensions.cs | 12 ++++++++++++ src/SoapCore/Meta/MetaBodyWriter.cs | 8 +++++--- src/SoapCore/SoapCoreOptions.cs | 8 ++++++++ src/SoapCore/SoapEndpointMiddleware.cs | 2 +- src/SoapCore/SoapOptions.cs | 3 +++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/SoapCore/Meta/BodyWriterExtensions.cs b/src/SoapCore/Meta/BodyWriterExtensions.cs index fc5232b3..1ba7f841 100644 --- a/src/SoapCore/Meta/BodyWriterExtensions.cs +++ b/src/SoapCore/Meta/BodyWriterExtensions.cs @@ -160,6 +160,18 @@ public static bool IsIgnored(this MemberInfo member) attr.AttributeType == typeof(XmlIgnoreAttribute)); } + public static bool IsIgnored(this MemberInfo member, bool xmlIgnoreOnly) + { + if (xmlIgnoreOnly) + { + return member + .CustomAttributes + .Any(attr => attr.AttributeType == typeof(XmlIgnoreAttribute)); + } + + return member.IsIgnored(); + } + /// /// Checks if the parent has a ShouldSerialize*() method defined for a specific member. /// diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index bb1b4e8c..ee7af350 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -34,9 +34,10 @@ public class MetaBodyWriter : BodyWriter private readonly Dictionary> _requestedDynamicTypes; private readonly bool _buildMicrosoftGuid = false; + private readonly bool _xmlIgnoreOnlyForWsdl = false; private IWsdlOperationNameGenerator _wsdlOperationNameGenerator; - public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlNamespaceLookup xmlNamespaceLookup, string bindingName, SoapBindingInfo[] soapBindings, bool buildMicrosoftGuid, IWsdlOperationNameGenerator wsdlOperationNameGenerator) : base(isBuffered: true) + public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlNamespaceLookup xmlNamespaceLookup, string bindingName, SoapBindingInfo[] soapBindings, bool buildMicrosoftGuid, IWsdlOperationNameGenerator wsdlOperationNameGenerator, bool xmlIgnoreOnlyForWsdl = false) : base(isBuffered: true) { _service = service; _baseUrl = baseUrl; @@ -52,6 +53,7 @@ public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlN PortName = bindingName; SoapBindings = soapBindings; _buildMicrosoftGuid = buildMicrosoftGuid; + _xmlIgnoreOnlyForWsdl = xmlIgnoreOnlyForWsdl; _wsdlOperationNameGenerator = wsdlOperationNameGenerator; } @@ -863,7 +865,7 @@ private void AddSchemaComplexType(XmlDictionaryWriter writer, TypeToBuild toBuil if (!isWrappedBodyType) { var propertyOrFieldMembers = toBuildBodyType.GetPropertyOrFieldMembers() - .Where(mi => !mi.IsIgnored() && mi.DeclaringType == toBuildType) + .Where(mi => !mi.IsIgnored(_xmlIgnoreOnlyForWsdl) && mi.DeclaringType == toBuildType) .ToList(); var elements = propertyOrFieldMembers.Where(t => !t.IsAttribute() && t.GetCustomAttribute() == null).ToList(); @@ -894,7 +896,7 @@ private void AddSchemaComplexType(XmlDictionaryWriter writer, TypeToBuild toBuil else { // TODO: should this also be changed to GetPropertyOrFieldMembers? - var properties = toBuildType.GetProperties().Where(prop => !prop.IsIgnored()) + var properties = toBuildType.GetProperties().Where(prop => !prop.IsIgnored(_xmlIgnoreOnlyForWsdl)) .ToList(); var elements = properties.Where(t => !t.IsAttribute()).ToList(); diff --git a/src/SoapCore/SoapCoreOptions.cs b/src/SoapCore/SoapCoreOptions.cs index 388f8106..1f10397a 100644 --- a/src/SoapCore/SoapCoreOptions.cs +++ b/src/SoapCore/SoapCoreOptions.cs @@ -148,6 +148,14 @@ public class SoapCoreOptions /// public string SchemeOverride { get; set; } + /// + /// When true, only [XmlIgnore] is used to exclude properties from WSDL generation, + /// ignoring [IgnoreDataMember]. This matches legacy WCF/ASMX XmlSerializer behavior + /// where [IgnoreDataMember] had no effect on XmlSerializer. + /// Defaults to false + /// + public bool XmlIgnoreOnlyForWsdl { get; set; } = false; + public void UseCustomSerializer() where TCustomSerializer : class, IXmlSerializationHandler { diff --git a/src/SoapCore/SoapEndpointMiddleware.cs b/src/SoapCore/SoapEndpointMiddleware.cs index 31cb407f..20a34f94 100644 --- a/src/SoapCore/SoapEndpointMiddleware.cs +++ b/src/SoapCore/SoapEndpointMiddleware.cs @@ -242,7 +242,7 @@ private async Task ProcessMeta(HttpContext httpContext, bool showDocumentation) var xmlNamespaceLookup = GetXmlNamespaceLookup(null); var bindingName = !string.IsNullOrWhiteSpace(_options.EncoderOptions[0].BindingName) ? _options.EncoderOptions[0].BindingName : "BasicHttpBinding_" + _service.GeneralContract.Name; var bodyWriter = _options.SoapSerializer == SoapSerializer.XmlSerializer - ? new MetaBodyWriter(_service, baseUrl, xmlNamespaceLookup, bindingName, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.UseMicrosoftGuid, _options.WsdlOperationNameGenerator) + ? new MetaBodyWriter(_service, baseUrl, xmlNamespaceLookup, bindingName, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.UseMicrosoftGuid, _options.WsdlOperationNameGenerator, _options.XmlIgnoreOnlyForWsdl) : (BodyWriter)new MetaWCFBodyWriter(_service, baseUrl, bindingName, _options.UseBasicAuthentication, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.WsdlOperationNameGenerator); //assumption that you want soap12 if your service supports that diff --git a/src/SoapCore/SoapOptions.cs b/src/SoapCore/SoapOptions.cs index 8f8ebf26..719b0830 100644 --- a/src/SoapCore/SoapOptions.cs +++ b/src/SoapCore/SoapOptions.cs @@ -71,6 +71,8 @@ public class SoapOptions public string SchemeOverride { get; set; } + public bool XmlIgnoreOnlyForWsdl { get; set; } = false; + [Obsolete] public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt) { @@ -105,6 +107,7 @@ public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt, Type serviceT GenerateSoapActionWithoutContractName = opt.GenerateSoapActionWithoutContractName, NormalizeNewLines = opt.NormalizeNewLines, SchemeOverride = opt.SchemeOverride, + XmlIgnoreOnlyForWsdl = opt.XmlIgnoreOnlyForWsdl, }; return options; From df1af8af93a3554a95a8067bca9ff8a428054b5f Mon Sep 17 00:00:00 2001 From: psalmnoelff Date: Fri, 6 Feb 2026 14:33:44 +0800 Subject: [PATCH 3/3] Add UseLegacyWsdlNaming option for WCF/ASMX-compatible WSDL names --- src/SoapCore/Meta/IWsdlOperationNameGenerator.cs | 13 +++++++++++++ src/SoapCore/Meta/MetaBodyWriter.cs | 6 ++++-- src/SoapCore/SoapCoreOptions.cs | 8 ++++++++ src/SoapCore/SoapEndpointMiddleware.cs | 2 +- src/SoapCore/SoapOptions.cs | 3 +++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/SoapCore/Meta/IWsdlOperationNameGenerator.cs b/src/SoapCore/Meta/IWsdlOperationNameGenerator.cs index 08b2f759..984763b0 100644 --- a/src/SoapCore/Meta/IWsdlOperationNameGenerator.cs +++ b/src/SoapCore/Meta/IWsdlOperationNameGenerator.cs @@ -20,4 +20,17 @@ public string GenerateWsdlOutputMessageName(OperationDescription operation, Serv return $"{service.GeneralContract.Name}_{operation.Name}_OutputMessage"; } } + + public class LegacyWsdlOperationNameGenerator : IWsdlOperationNameGenerator + { + public string GenerateWsdlInputMessageName(OperationDescription operation, ServiceDescription service) + { + return $"{operation.Name}SoapIn"; + } + + public string GenerateWsdlOutputMessageName(OperationDescription operation, ServiceDescription service) + { + return $"{operation.Name}SoapOut"; + } + } } diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index ee7af350..2a52e445 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -36,8 +36,9 @@ public class MetaBodyWriter : BodyWriter private readonly bool _buildMicrosoftGuid = false; private readonly bool _xmlIgnoreOnlyForWsdl = false; private IWsdlOperationNameGenerator _wsdlOperationNameGenerator; + private readonly string _portTypeSuffix; - public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlNamespaceLookup xmlNamespaceLookup, string bindingName, SoapBindingInfo[] soapBindings, bool buildMicrosoftGuid, IWsdlOperationNameGenerator wsdlOperationNameGenerator, bool xmlIgnoreOnlyForWsdl = false) : base(isBuffered: true) + public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlNamespaceLookup xmlNamespaceLookup, string bindingName, SoapBindingInfo[] soapBindings, bool buildMicrosoftGuid, IWsdlOperationNameGenerator wsdlOperationNameGenerator, bool xmlIgnoreOnlyForWsdl = false, string portTypeSuffix = "") : base(isBuffered: true) { _service = service; _baseUrl = baseUrl; @@ -55,11 +56,12 @@ public MetaBodyWriter(ServiceDescription service, string baseUrl, ConcurrentXmlN _buildMicrosoftGuid = buildMicrosoftGuid; _xmlIgnoreOnlyForWsdl = xmlIgnoreOnlyForWsdl; _wsdlOperationNameGenerator = wsdlOperationNameGenerator; + _portTypeSuffix = portTypeSuffix ?? ""; } private SoapBindingInfo[] SoapBindings { get; } private string BindingName { get; } - private string BindingType => _service.GeneralContract.Name; + private string BindingType => _service.GeneralContract.Name + _portTypeSuffix; private string PortName { get; } private string TargetNameSpace => _service.GeneralContract.Namespace; diff --git a/src/SoapCore/SoapCoreOptions.cs b/src/SoapCore/SoapCoreOptions.cs index 1f10397a..148fd37a 100644 --- a/src/SoapCore/SoapCoreOptions.cs +++ b/src/SoapCore/SoapCoreOptions.cs @@ -156,6 +156,14 @@ public class SoapCoreOptions /// public bool XmlIgnoreOnlyForWsdl { get; set; } = false; + /// + /// When true, uses legacy WCF/ASMX WSDL naming conventions: + /// portType name becomes {ContractName}Soap, and message names + /// use {OperationName}SoapIn/{OperationName}SoapOut pattern. + /// Defaults to false + /// + public bool UseLegacyWsdlNaming { get; set; } = false; + public void UseCustomSerializer() where TCustomSerializer : class, IXmlSerializationHandler { diff --git a/src/SoapCore/SoapEndpointMiddleware.cs b/src/SoapCore/SoapEndpointMiddleware.cs index 20a34f94..e495aed0 100644 --- a/src/SoapCore/SoapEndpointMiddleware.cs +++ b/src/SoapCore/SoapEndpointMiddleware.cs @@ -242,7 +242,7 @@ private async Task ProcessMeta(HttpContext httpContext, bool showDocumentation) var xmlNamespaceLookup = GetXmlNamespaceLookup(null); var bindingName = !string.IsNullOrWhiteSpace(_options.EncoderOptions[0].BindingName) ? _options.EncoderOptions[0].BindingName : "BasicHttpBinding_" + _service.GeneralContract.Name; var bodyWriter = _options.SoapSerializer == SoapSerializer.XmlSerializer - ? new MetaBodyWriter(_service, baseUrl, xmlNamespaceLookup, bindingName, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.UseMicrosoftGuid, _options.WsdlOperationNameGenerator, _options.XmlIgnoreOnlyForWsdl) + ? new MetaBodyWriter(_service, baseUrl, xmlNamespaceLookup, bindingName, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.UseMicrosoftGuid, _options.UseLegacyWsdlNaming ? new LegacyWsdlOperationNameGenerator() : _options.WsdlOperationNameGenerator, _options.XmlIgnoreOnlyForWsdl, _options.UseLegacyWsdlNaming ? "Soap" : "") : (BodyWriter)new MetaWCFBodyWriter(_service, baseUrl, bindingName, _options.UseBasicAuthentication, _messageEncoders.Select(me => new SoapBindingInfo(me.MessageVersion, me.BindingName, me.PortName)).ToArray(), _options.WsdlOperationNameGenerator); //assumption that you want soap12 if your service supports that diff --git a/src/SoapCore/SoapOptions.cs b/src/SoapCore/SoapOptions.cs index 719b0830..6feeb038 100644 --- a/src/SoapCore/SoapOptions.cs +++ b/src/SoapCore/SoapOptions.cs @@ -73,6 +73,8 @@ public class SoapOptions public bool XmlIgnoreOnlyForWsdl { get; set; } = false; + public bool UseLegacyWsdlNaming { get; set; } = false; + [Obsolete] public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt) { @@ -108,6 +110,7 @@ public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt, Type serviceT NormalizeNewLines = opt.NormalizeNewLines, SchemeOverride = opt.SchemeOverride, XmlIgnoreOnlyForWsdl = opt.XmlIgnoreOnlyForWsdl, + UseLegacyWsdlNaming = opt.UseLegacyWsdlNaming, }; return options;