diff --git a/README.md b/README.md index 28bbbae3..a9979a3f 100644 --- a/README.md +++ b/README.md @@ -78,22 +78,26 @@ To use it, add a setting like this to appsettings ```csharp "FileWSDL": { "UrlOverride": "", + "VirtualPath": "", "WebServiceWSDLMapping": { - "Service.asmx": { + "Service.asmx": { , + "UrlOverride": "Management/Service.asmx", "WsdlFile": "snapshotpull.wsdl", "SchemaFolder": "Schemas", "WsdlFolder": "Schemas" } - }, - "VirtualPath": "" + } ``` * UrlOverride - can be used to override the URL in the service description. This can be useful if you are behind a firewall. -* Service.asmx - is the endpoint of the service you expose. You can have more than one. -* WsdlFile - is the name of the WSDL on disc. -* SchemaFolder - if you import XSD from WSDL, this is the folder where the Schemas are stored on disc. -* WsdlFolder - is the folder that the WSDL file is stored on disc. -* VirualPath - can be used if you like to add a path between the base URL and service. +* VirualPath - can be used if you like to add a path between the base URL and service. +* WebServiceWSDLMapping + * UrlOverride - can be used to override the URL for a specific WSDL mapping. This can be useful if you want to host different services under different folder. + * Service.asmx - is the endpoint of the service you expose. You can have more than one. + * WsdlFile - is the name of the WSDL on disc. + * SchemaFolder - if you import XSD from WSDL, this is the folder where the Schemas are stored on disc. + * WsdlFolder - is the folder that the WSDL file is stored on disc. + To read the setting you can do the following @@ -101,8 +105,11 @@ In Startup.cs: ```csharp - var settings = Configuration.GetSection("FileWSDL").Get(); + +// For case-insensitive mapping, if you are using "SoapCoreOptions.CaseInsensitivePath = true" - otherwise URLs with different casing won't be mapped correctly +//var settings = Configuration.GetSection("FileWSDL").Get(); + settings.AppPath = env.ContentRootPath; // The hosting environment root path ... diff --git a/src/SoapCore.Tests/Wsdl/Services/DefaultValuesAttributesService.cs b/src/SoapCore.Tests/Wsdl/Services/DefaultValuesAttributesService.cs new file mode 100644 index 00000000..cd68219b --- /dev/null +++ b/src/SoapCore.Tests/Wsdl/Services/DefaultValuesAttributesService.cs @@ -0,0 +1,49 @@ +using System.ComponentModel; +using System.ServiceModel; + +namespace SoapCore.Tests.Wsdl.Services +{ +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type + [ServiceContract(Namespace = "http://bagov.net")] + public interface IDefaultValueAttributesService + { + [OperationContract] + DefaultValueAttributesResponseType GetResponse(); + } + + public class DefaultValueAttributesService : IDefaultValueAttributesService + { + public DefaultValueAttributesResponseType GetResponse() + { + return new DefaultValueAttributesResponseType(); + } + } + + public class DefaultValueAttributesResponseType + { + public bool BooleanWithNoDefaultProperty { get; set; } + + [DefaultValue(null)] + public bool BooleanWithDefaultNullProperty { get; set; } + + [DefaultValue(false)] + public bool BooleanWithDefaultFalseProperty { get; set; } + + [DefaultValue(true)] + public string BooleanWithDefaultTrueProperty { get; set; } + + public int IntWithNoDefaultProperty { get; set; } + + [DefaultValue(42)] + public int IntWithDefaultProperty { get; set; } + + public string StringWithNoDefaultProperty { get; set; } + + [DefaultValue(null)] + public string StringWithDefaultNullProperty { get; set; } + + [DefaultValue("default")] + public string StringWithDefaultProperty { get; set; } + } +} diff --git a/src/SoapCore.Tests/Wsdl/WsdlTests.cs b/src/SoapCore.Tests/Wsdl/WsdlTests.cs index 221562ce..ca534c8d 100644 --- a/src/SoapCore.Tests/Wsdl/WsdlTests.cs +++ b/src/SoapCore.Tests/Wsdl/WsdlTests.cs @@ -748,6 +748,46 @@ public async Task CheckMessageHeadersServiceWsdl() Assert.IsNotNull(stringPropertyElement); } + [TestMethod] + public async Task CheckDefaultValueAttributesServiceWsdl() + { + var wsdl = await GetWsdlFromMetaBodyWriter(SoapSerializer.XmlSerializer); + Trace.TraceInformation(wsdl); + Assert.IsNotNull(wsdl); + + Assert.IsFalse(wsdl.Contains("name=\"\"")); + + var root = XElement.Parse(wsdl); + var nm = Namespaces.CreateDefaultXmlNamespaceManager(); + + var booleanWithNoDefaultPropertyElement = root.XPathSelectElement("//xsd:element[@name='BooleanWithNoDefaultProperty' and @minOccurs='1' and @maxOccurs='1' and not(@default)]", nm); + Assert.IsNotNull(booleanWithNoDefaultPropertyElement); + + var booleanWithDefaultNullPropertyElement = root.XPathSelectElement("//xsd:element[@name='BooleanWithDefaultNullProperty' and @minOccurs='1' and @maxOccurs='1' and not(@default)]", nm); + Assert.IsNotNull(booleanWithDefaultNullPropertyElement); + + var booleanWithDefaultFalsePropertyElement = root.XPathSelectElement("//xsd:element[@name='BooleanWithDefaultFalseProperty' and @minOccurs='0' and @maxOccurs='1' and @default='false']", nm); + Assert.IsNotNull(booleanWithDefaultFalsePropertyElement); + + var booleanWithDefaultTruePropertyElement = root.XPathSelectElement("//xsd:element[@name='BooleanWithDefaultTrueProperty' and @minOccurs='0' and @maxOccurs='1' and @default='true']", nm); + Assert.IsNotNull(booleanWithDefaultTruePropertyElement); + + var intWithNoDefaultPropertyElement = root.XPathSelectElement("//xsd:element[@name='IntWithNoDefaultProperty' and @minOccurs='1' and @maxOccurs='1' and not(@default)]", nm); + Assert.IsNotNull(intWithNoDefaultPropertyElement); + + var intWithDefaultPropertyElement = root.XPathSelectElement("//xsd:element[@name='IntWithDefaultProperty' and @minOccurs='0' and @maxOccurs='1' and @default='42']", nm); + Assert.IsNotNull(intWithDefaultPropertyElement); + + var stringWithNoDefaultPropertyElement = root.XPathSelectElement("//xsd:element[@name='StringWithNoDefaultProperty' and @minOccurs='0' and @maxOccurs='1' and not(@default)]", nm); + Assert.IsNotNull(stringWithNoDefaultPropertyElement); + + var stringWithDefaultNullPropertyElement = root.XPathSelectElement("//xsd:element[@name='StringWithDefaultNullProperty' and @minOccurs='0' and @maxOccurs='1' and not(@default)]", nm); + Assert.IsNotNull(stringWithDefaultNullPropertyElement); + + var stringWithDefaultPropertyElement = root.XPathSelectElement("//xsd:element[@name='StringWithDefaultProperty' and @minOccurs='0' and @maxOccurs='1' and @default='default']", nm); + Assert.IsNotNull(stringWithDefaultPropertyElement); + } + [TestMethod] public async Task CheckDataContractKnownTypeAttributeServiceWsdl() { diff --git a/src/SoapCore.Tests/WsdlFromFile/Startup.cs b/src/SoapCore.Tests/WsdlFromFile/Startup.cs index 113d39fd..855a03e5 100644 --- a/src/SoapCore.Tests/WsdlFromFile/Startup.cs +++ b/src/SoapCore.Tests/WsdlFromFile/Startup.cs @@ -43,7 +43,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF { SchemaFolder = "/WsdlFromFile/WSDL", WsdlFile = _wsdlFile, - WSDLFolder = "/WsdlFromFile/WSDL" + WSDLFolder = "/WsdlFromFile/WSDL", + UrlOverride = "Management/Service.asmx" } } }, @@ -69,7 +70,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF { SchemaFolder = "/WsdlFromFile/WSDL", WsdlFile = _wsdlFile, - WSDLFolder = "/WsdlFromFile/WSDL" + WSDLFolder = "/WsdlFromFile/WSDL", + UrlOverride = "Management/Service.asmx" } } }, diff --git a/src/SoapCore.Tests/WsdlFromFile/WsdlIncludeTests.cs b/src/SoapCore.Tests/WsdlFromFile/WsdlIncludeTests.cs index 9c7bf52b..4894be4e 100644 --- a/src/SoapCore.Tests/WsdlFromFile/WsdlIncludeTests.cs +++ b/src/SoapCore.Tests/WsdlFromFile/WsdlIncludeTests.cs @@ -41,7 +41,7 @@ public void CheckXSDInclude() var addresses = _host.ServerFeatures.Get(); var address = addresses.Addresses.Single(); - string url = address + "/Service.asmx?xsd&name=echoInclude.xsd"; + string url = address + "/Management/Service.asmx?xsd&name=echoInclude.xsd"; Assert.IsNotNull(element); Assert.AreEqual(url, element.Attributes["schemaLocation"]?.Value); diff --git a/src/SoapCore.Tests/WsdlFromFile/WsdlTests.cs b/src/SoapCore.Tests/WsdlFromFile/WsdlTests.cs index 0f0b8317..07b5c430 100644 --- a/src/SoapCore.Tests/WsdlFromFile/WsdlTests.cs +++ b/src/SoapCore.Tests/WsdlFromFile/WsdlTests.cs @@ -75,7 +75,7 @@ public void CheckAddressLocation() var addresses = _host.ServerFeatures.Get(); var address = addresses.Addresses.Single(); - string url = address + "/Service.asmx"; + string url = address + "/Management/Service.asmx"; Assert.IsNotNull(element); Assert.AreEqual(element.Attributes["location"]?.Value, url); } @@ -100,7 +100,7 @@ public void CheckXSDImport() var addresses = _host.ServerFeatures.Get(); var address = addresses.Addresses.Single(); - string url = address + "/Service.asmx?xsd&name=DATEXII_3_MessageContainer.xsd"; + string url = address + "/Management/Service.asmx?xsd&name=DATEXII_3_MessageContainer.xsd"; Assert.IsNotNull(element); Assert.AreEqual(element.Attributes["namespace"]?.Value, "http://datex2.eu/schema/3/messageContainer"); diff --git a/src/SoapCore/MessageEncoder/SoapMessageEncoder.cs b/src/SoapCore/MessageEncoder/SoapMessageEncoder.cs index bf6c7516..e310af6f 100644 --- a/src/SoapCore/MessageEncoder/SoapMessageEncoder.cs +++ b/src/SoapCore/MessageEncoder/SoapMessageEncoder.cs @@ -1,322 +1,329 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.IO.Pipelines; -using System.Linq; -using System.Net.Http.Headers; -using System.ServiceModel; -using System.ServiceModel.Channels; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.AspNetCore.Http; - -namespace SoapCore.MessageEncoder -{ - public class SoapMessageEncoder - { - internal const string Soap11MediaType = "text/xml"; - internal const string Soap12MediaType = "application/soap+xml"; - private const string XmlMediaType = "application/xml"; - - private readonly Encoding _writeEncoding; - private readonly bool _optimizeWriteForUtf8; - private readonly bool _omitXmlDeclaration; - private readonly bool _indentXml; - private readonly bool _supportXmlDictionaryReader; - private readonly bool _checkXmlCharacters; - - public SoapMessageEncoder(MessageVersion version, Encoding writeEncoding, XmlDictionaryReaderQuotas quotas, bool omitXmlDeclaration, bool indentXml, bool checkXmlCharacters, XmlNamespaceManager xmlNamespaceOverrides, string bindingName, string portName, int maxSoapHeaderSize = SoapMessageEncoderDefaults.MaxSoapHeaderSizeDefault) - { - _indentXml = indentXml; - _omitXmlDeclaration = omitXmlDeclaration; - _checkXmlCharacters = checkXmlCharacters; - BindingName = bindingName; - PortName = portName; - - if (writeEncoding == null) - { - throw new ArgumentNullException(nameof(writeEncoding)); - } - - _supportXmlDictionaryReader = SoapMessageEncoderDefaults.TryValidateEncoding(writeEncoding, out _); - - _writeEncoding = writeEncoding; - _optimizeWriteForUtf8 = IsUtf8Encoding(writeEncoding); - - MessageVersion = version ?? throw new ArgumentNullException(nameof(version)); - - ReaderQuotas = new XmlDictionaryReaderQuotas(); - (quotas ?? XmlDictionaryReaderQuotas.Max).CopyTo(ReaderQuotas); - MaxSoapHeaderSize = maxSoapHeaderSize; - - MediaType = GetMediaType(version); - CharSet = SoapMessageEncoderDefaults.EncodingToCharSet(writeEncoding); - ContentType = GetContentType(MediaType, CharSet); - - XmlNamespaceOverrides = xmlNamespaceOverrides; - } - - public string BindingName { get; } - public string PortName { get; } - - public string ContentType { get; } - - public string MediaType { get; } - - public string CharSet { get; } - - public MessageVersion MessageVersion { get; } - - public XmlDictionaryReaderQuotas ReaderQuotas { get; } - - public int MaxSoapHeaderSize { get; } - - public XmlNamespaceManager XmlNamespaceOverrides { get; } - - public bool IsContentTypeSupported(string contentType, bool checkCharset) - { - if (contentType == null) - { - throw new ArgumentNullException(nameof(contentType)); - } - - if (IsContentTypeSupported(contentType, ContentType, MediaType, checkCharset)) - { - return true; - } - - // we support a few extra content types for "none" - if (MessageVersion.Equals(MessageVersion.None)) - { - const string rss1MediaType = "text/xml"; - const string rss2MediaType = "application/rss+xml"; - const string atomMediaType = "application/atom+xml"; - const string htmlMediaType = "text/html"; - - if (IsContentTypeSupported(contentType, rss1MediaType, rss1MediaType, checkCharset)) - { - return true; - } - - if (IsContentTypeSupported(contentType, rss2MediaType, rss2MediaType, checkCharset)) - { - return true; - } - - if (IsContentTypeSupported(contentType, htmlMediaType, atomMediaType, checkCharset)) - { - return true; - } - - if (IsContentTypeSupported(contentType, atomMediaType, atomMediaType, checkCharset)) - { - return true; - } - } - - return false; - } - - public async Task ReadMessageAsync(PipeReader pipeReader, int maxSizeOfHeaders, string contentType) - { - if (pipeReader == null) - { - throw new ArgumentNullException(nameof(pipeReader)); - } - - using var stream = pipeReader.AsStream(true); - return await ReadMessageAsync(stream, maxSizeOfHeaders, contentType); - } - - public Task ReadMessageAsync(Stream stream, int maxSizeOfHeaders, string contentType) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - XmlReader reader = _supportXmlDictionaryReader ? - XmlDictionaryReader.CreateTextReader(stream, _writeEncoding, ReaderQuotas, dictionaryReader => { }) : - XmlReader.Create(stream, new XmlReaderSettings() { IgnoreWhitespace = true, DtdProcessing = DtdProcessing.Prohibit }); - - Message message = Message.CreateMessage(reader, maxSizeOfHeaders, MessageVersion); - - return Task.FromResult(message); - } - - public virtual async Task WriteMessageAsync(Message message, HttpContext httpContext, PipeWriter pipeWriter) - { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (pipeWriter == null) - { - throw new ArgumentNullException(nameof(pipeWriter)); - } - - ThrowIfMismatchedMessageVersion(message); - - //Custom string writer with custom encoding support - using (var stringWriter = new CustomStringWriter(_writeEncoding)) - { - using (var xmlTextWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings - { - OmitXmlDeclaration = _optimizeWriteForUtf8 && _omitXmlDeclaration, //can only omit if utf-8 - Indent = _indentXml, - Encoding = _writeEncoding, - CloseOutput = true, - CheckCharacters = _checkXmlCharacters - })) - { - using var xmlWriter = XmlDictionaryWriter.CreateDictionaryWriter(xmlTextWriter); - message.WriteMessage(xmlWriter); - xmlWriter.WriteEndDocument(); - xmlWriter.Flush(); - } - - var data = stringWriter.ToString(); - var soapMessage = _writeEncoding.GetBytes(data); - - //Set Content-length in Response - httpContext.Response.ContentLength = soapMessage.Length; - - await pipeWriter.WriteAsync(soapMessage); - await pipeWriter.FlushAsync(); - } - } - - public virtual Task WriteMessageAsync(Message message, Stream stream) - { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - ThrowIfMismatchedMessageVersion(message); - - using var xmlTextWriter = XmlWriter.Create(stream, new XmlWriterSettings - { - OmitXmlDeclaration = _optimizeWriteForUtf8 && _omitXmlDeclaration, //can only omit if utf-8, - Indent = _indentXml, - Encoding = _writeEncoding, - CloseOutput = false, - CheckCharacters = _checkXmlCharacters - }); - - using var xmlWriter = XmlDictionaryWriter.CreateDictionaryWriter(xmlTextWriter); - message.WriteMessage(xmlWriter); - xmlWriter.WriteEndDocument(); - xmlWriter.Flush(); - - return Task.CompletedTask; - } - - internal static string GetMediaType(MessageVersion version) - { - string mediaType; - - if (version.Envelope == EnvelopeVersion.Soap12) - { - mediaType = Soap12MediaType; - } - else if (version.Envelope == EnvelopeVersion.Soap11) - { - mediaType = Soap11MediaType; - } - else if (version.Envelope == EnvelopeVersion.None) - { - mediaType = XmlMediaType; - } - else - { - throw new InvalidOperationException($"Envelope Version '{version.Envelope}' is not supported."); - } - - return mediaType; - } - - internal static string GetContentType(string mediaType, string charSet) - { - return string.Format(CultureInfo.InvariantCulture, "{0}; charset={1}", mediaType, charSet); - } - - internal bool IsContentTypeSupported(string contentType, string supportedContentType, string supportedMediaType, bool checkCharset) - { - if (supportedContentType == contentType) - { - return true; - } - - MediaTypeHeaderValue parsedContentType = null; - - try - { - parsedContentType = MediaTypeHeaderValue.Parse(contentType); - } - catch (FormatException) - { - //bad format - return false; - } - - if (parsedContentType.MediaType.Equals(MediaType, StringComparison.OrdinalIgnoreCase)) - { - if (!checkCharset || string.IsNullOrWhiteSpace(parsedContentType.CharSet) || parsedContentType.CharSet.Equals(CharSet, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - // sometimes we get a contentType that has parameters, but our encoders - // merely expose the base content-type, so we will check a stripped version - if (supportedMediaType.Length > 0 && !supportedMediaType.Equals(parsedContentType.MediaType, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (!IsCharSetSupported(parsedContentType.CharSet)) - { - return false; - } - - return true; - } - - internal virtual bool IsCharSetSupported(string charset) - { - return CharSet?.Equals(charset, StringComparison.OrdinalIgnoreCase) - ?? false; - } - - private static bool IsUtf8Encoding(Encoding encoding) - { - return encoding.WebName == "utf-8"; - } - - private void ThrowIfMismatchedMessageVersion(Message message) - { - if (!message.Version.Equals(MessageVersion)) - { - throw new InvalidOperationException($"Message version {message.Version.Envelope} doesn't match encoder version {message.Version.Envelope}"); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Pipelines; +using System.Linq; +using System.Net.Http.Headers; +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.AspNetCore.Http; + +namespace SoapCore.MessageEncoder +{ + public class SoapMessageEncoder + { + internal const string Soap11MediaType = "text/xml"; + internal const string Soap12MediaType = "application/soap+xml"; + private const string XmlMediaType = "application/xml"; + + private readonly Encoding _writeEncoding; + private readonly bool _optimizeWriteForUtf8; + private readonly bool _omitXmlDeclaration; + private readonly bool _indentXml; + private readonly bool _supportXmlDictionaryReader; + private readonly bool _checkXmlCharacters; + + public SoapMessageEncoder(MessageVersion version, Encoding writeEncoding, XmlDictionaryReaderQuotas quotas, bool omitXmlDeclaration, bool indentXml, bool checkXmlCharacters, XmlNamespaceManager xmlNamespaceOverrides, string bindingName, string portName, int maxSoapHeaderSize = SoapMessageEncoderDefaults.MaxSoapHeaderSizeDefault) + { + _indentXml = indentXml; + _omitXmlDeclaration = omitXmlDeclaration; + _checkXmlCharacters = checkXmlCharacters; + BindingName = bindingName; + PortName = portName; + + if (writeEncoding == null) + { + throw new ArgumentNullException(nameof(writeEncoding)); + } + + _supportXmlDictionaryReader = SoapMessageEncoderDefaults.TryValidateEncoding(writeEncoding, out _); + + _writeEncoding = writeEncoding; + _optimizeWriteForUtf8 = IsUtf8Encoding(writeEncoding); + + MessageVersion = version ?? throw new ArgumentNullException(nameof(version)); + + ReaderQuotas = new XmlDictionaryReaderQuotas(); + (quotas ?? XmlDictionaryReaderQuotas.Max).CopyTo(ReaderQuotas); + MaxSoapHeaderSize = maxSoapHeaderSize; + + MediaType = GetMediaType(version); + CharSet = SoapMessageEncoderDefaults.EncodingToCharSet(writeEncoding); + ContentType = GetContentType(MediaType, CharSet); + + XmlNamespaceOverrides = xmlNamespaceOverrides; + } + + public string BindingName { get; } + public string PortName { get; } + + public string ContentType { get; } + + public string MediaType { get; } + + public string CharSet { get; } + + public MessageVersion MessageVersion { get; } + + public XmlDictionaryReaderQuotas ReaderQuotas { get; } + + public int MaxSoapHeaderSize { get; } + + public XmlNamespaceManager XmlNamespaceOverrides { get; } + + public bool IsContentTypeSupported(string contentType, bool checkCharset) + { + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + if (IsContentTypeSupported(contentType, ContentType, MediaType, checkCharset)) + { + return true; + } + + // we support a few extra content types for "none" + if (MessageVersion.Equals(MessageVersion.None)) + { + const string rss1MediaType = "text/xml"; + const string rss2MediaType = "application/rss+xml"; + const string atomMediaType = "application/atom+xml"; + const string htmlMediaType = "text/html"; + + if (IsContentTypeSupported(contentType, rss1MediaType, rss1MediaType, checkCharset)) + { + return true; + } + + if (IsContentTypeSupported(contentType, rss2MediaType, rss2MediaType, checkCharset)) + { + return true; + } + + if (IsContentTypeSupported(contentType, htmlMediaType, atomMediaType, checkCharset)) + { + return true; + } + + if (IsContentTypeSupported(contentType, atomMediaType, atomMediaType, checkCharset)) + { + return true; + } + } + + return false; + } + + public async Task ReadMessageAsync(PipeReader pipeReader, int maxSizeOfHeaders, string contentType) + { + if (pipeReader == null) + { + throw new ArgumentNullException(nameof(pipeReader)); + } + + using var stream = pipeReader.AsStream(true); + return await ReadMessageAsync(stream, maxSizeOfHeaders, contentType); + } + + public Task ReadMessageAsync(Stream stream, int maxSizeOfHeaders, string contentType) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + XmlReader reader; + if (_supportXmlDictionaryReader) + { + reader = XmlDictionaryReader.CreateTextReader(stream, _writeEncoding, ReaderQuotas, dictionaryReader => { }); + } + else + { + var streamReaderWithEncoding = new StreamReader(stream, _writeEncoding); + reader = XmlReader.Create(streamReaderWithEncoding, new XmlReaderSettings() { IgnoreWhitespace = true, DtdProcessing = DtdProcessing.Prohibit, CloseInput = true }); + } + + Message message = Message.CreateMessage(reader, maxSizeOfHeaders, MessageVersion); + + return Task.FromResult(message); + } + + public virtual async Task WriteMessageAsync(Message message, HttpContext httpContext, PipeWriter pipeWriter) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (pipeWriter == null) + { + throw new ArgumentNullException(nameof(pipeWriter)); + } + + ThrowIfMismatchedMessageVersion(message); + + //Custom string writer with custom encoding support + using (var stringWriter = new CustomStringWriter(_writeEncoding)) + { + using (var xmlTextWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings + { + OmitXmlDeclaration = _optimizeWriteForUtf8 && _omitXmlDeclaration, //can only omit if utf-8 + Indent = _indentXml, + Encoding = _writeEncoding, + CloseOutput = true, + CheckCharacters = _checkXmlCharacters + })) + { + using var xmlWriter = XmlDictionaryWriter.CreateDictionaryWriter(xmlTextWriter); + message.WriteMessage(xmlWriter); + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + } + + var data = stringWriter.ToString(); + var soapMessage = _writeEncoding.GetBytes(data); + + //Set Content-length in Response + httpContext.Response.ContentLength = soapMessage.Length; + + await pipeWriter.WriteAsync(soapMessage); + await pipeWriter.FlushAsync(); + } + } + + public virtual Task WriteMessageAsync(Message message, Stream stream) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + ThrowIfMismatchedMessageVersion(message); + + using var xmlTextWriter = XmlWriter.Create(stream, new XmlWriterSettings + { + OmitXmlDeclaration = _optimizeWriteForUtf8 && _omitXmlDeclaration, //can only omit if utf-8, + Indent = _indentXml, + Encoding = _writeEncoding, + CloseOutput = false, + CheckCharacters = _checkXmlCharacters + }); + + using var xmlWriter = XmlDictionaryWriter.CreateDictionaryWriter(xmlTextWriter); + message.WriteMessage(xmlWriter); + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + + return Task.CompletedTask; + } + + internal static string GetMediaType(MessageVersion version) + { + string mediaType; + + if (version.Envelope == EnvelopeVersion.Soap12) + { + mediaType = Soap12MediaType; + } + else if (version.Envelope == EnvelopeVersion.Soap11) + { + mediaType = Soap11MediaType; + } + else if (version.Envelope == EnvelopeVersion.None) + { + mediaType = XmlMediaType; + } + else + { + throw new InvalidOperationException($"Envelope Version '{version.Envelope}' is not supported."); + } + + return mediaType; + } + + internal static string GetContentType(string mediaType, string charSet) + { + return string.Format(CultureInfo.InvariantCulture, "{0}; charset={1}", mediaType, charSet); + } + + internal bool IsContentTypeSupported(string contentType, string supportedContentType, string supportedMediaType, bool checkCharset) + { + if (supportedContentType == contentType) + { + return true; + } + + MediaTypeHeaderValue parsedContentType = null; + + try + { + parsedContentType = MediaTypeHeaderValue.Parse(contentType); + } + catch (FormatException) + { + //bad format + return false; + } + + if (parsedContentType.MediaType.Equals(MediaType, StringComparison.OrdinalIgnoreCase)) + { + if (!checkCharset || string.IsNullOrWhiteSpace(parsedContentType.CharSet) || parsedContentType.CharSet.Equals(CharSet, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + // sometimes we get a contentType that has parameters, but our encoders + // merely expose the base content-type, so we will check a stripped version + if (supportedMediaType.Length > 0 && !supportedMediaType.Equals(parsedContentType.MediaType, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!IsCharSetSupported(parsedContentType.CharSet)) + { + return false; + } + + return true; + } + + internal virtual bool IsCharSetSupported(string charset) + { + return CharSet?.Equals(charset, StringComparison.OrdinalIgnoreCase) + ?? false; + } + + private static bool IsUtf8Encoding(Encoding encoding) + { + return encoding.WebName == "utf-8"; + } + + private void ThrowIfMismatchedMessageVersion(Message message) + { + if (!message.Version.Equals(MessageVersion)) + { + throw new InvalidOperationException($"Message version {message.Version.Envelope} doesn't match encoder version {message.Version.Envelope}"); + } + } + } +} diff --git a/src/SoapCore/Meta/MetaBodyWriter.cs b/src/SoapCore/Meta/MetaBodyWriter.cs index 43cf9489..974374fa 100644 --- a/src/SoapCore/Meta/MetaBodyWriter.cs +++ b/src/SoapCore/Meta/MetaBodyWriter.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; @@ -875,7 +876,20 @@ private void AddSchemaTypePropertyOrField(XmlDictionaryWriter writer, MemberInfo } else { - AddSchemaType(writer, toBuild, parentTypeToBuild.ChildElementName ?? member.Name, isArray: createListWithoutProxyType, isListWithoutWrapper: createListWithoutProxyType, isUnqualified: isUnqualified); + string defaultValue = null; + var defaultAttributeValue = member.GetCustomAttribute()?.Value; + if (defaultAttributeValue != null) + { + if (defaultAttributeValue is bool value) + { + defaultValue = value ? "true" : "false"; + } + else + { + defaultValue = defaultAttributeValue.ToString(); + } + } + AddSchemaType(writer, toBuild, parentTypeToBuild.ChildElementName ?? member.Name, isArray: createListWithoutProxyType, isListWithoutWrapper: createListWithoutProxyType, isUnqualified: isUnqualified, defaultValue: defaultValue); } } @@ -895,7 +909,7 @@ private void AddSchemaType(XmlDictionaryWriter writer, Type type, string name, b AddSchemaType(writer, new TypeToBuild(type), name, isArray, @namespace, isAttribute, isUnqualified: isUnqualified); } - private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, string name, bool isArray = false, string @namespace = null, bool isAttribute = false, bool isListWithoutWrapper = false, bool isUnqualified = false) + private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, string name, bool isArray = false, string @namespace = null, bool isAttribute = false, bool isListWithoutWrapper = false, bool isUnqualified = false, string defaultValue = null) { var type = toBuild.Type; @@ -982,8 +996,12 @@ private void AddSchemaType(XmlDictionaryWriter writer, TypeToBuild toBuild, stri } else { - writer.WriteAttributeString("minOccurs", type.IsValueType ? "1" : "0"); + writer.WriteAttributeString("minOccurs", type.IsValueType && defaultValue == null ? "1" : "0"); writer.WriteAttributeString("maxOccurs", "1"); + if (defaultValue != null) + { + writer.WriteAttributeString("default", defaultValue); + } } if (string.IsNullOrEmpty(name)) diff --git a/src/SoapCore/Meta/MetaMessage.cs b/src/SoapCore/Meta/MetaMessage.cs index 3fa3174a..2c651e3e 100644 --- a/src/SoapCore/Meta/MetaMessage.cs +++ b/src/SoapCore/Meta/MetaMessage.cs @@ -81,6 +81,7 @@ protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer) writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); + writer.WriteEndElement(); } } diff --git a/src/SoapCore/Meta/MetaWCFBodyWriter.cs b/src/SoapCore/Meta/MetaWCFBodyWriter.cs index 2af216ff..f88018ee 100644 --- a/src/SoapCore/Meta/MetaWCFBodyWriter.cs +++ b/src/SoapCore/Meta/MetaWCFBodyWriter.cs @@ -190,6 +190,8 @@ private void WriteParameters(XmlDictionaryWriter writer, SoapMethodParameterInfo { foreach (var parameterInfo in parameterInfos) { + if (parameterInfo.Parameter.ParameterType.FullName == "System.Threading.CancellationToken") + continue; var elementAttribute = parameterInfo.Parameter.GetCustomAttribute(); var parameterName = !string.IsNullOrEmpty(elementAttribute?.ElementName) ? elementAttribute.ElementName @@ -268,6 +270,8 @@ private void AddContractOperations(XmlDictionaryWriter writer, ContractDescripti { var type = parameter.Parameter.ParameterType; var typeInfo = type.GetTypeInfo(); + if (typeInfo.FullName == "System.Threading.CancellationToken") + continue; if (typeInfo.IsByRef) { type = typeInfo.GetElementType(); @@ -308,7 +312,7 @@ private void AddContractOperations(XmlDictionaryWriter writer, ContractDescripti 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()) + foreach (var @namespace in groupedByNamespace.Keys.Where(x => x != null && x != _service.ServiceType.Namespace && x != contract.Namespace).Distinct()) { writer.WriteStartElement("xs", "import", Namespaces.XMLNS_XSD); writer.WriteAttributeString("namespace", @namespace); diff --git a/src/SoapCore/SoapCore.csproj b/src/SoapCore/SoapCore.csproj index 92f7f9b2..5f8646d8 100644 --- a/src/SoapCore/SoapCore.csproj +++ b/src/SoapCore/SoapCore.csproj @@ -2,7 +2,7 @@ SOAP protocol middleware for ASP.NET Core - 1.1.0.31 + 1.1.0.34 Digital Design netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0 SoapCore diff --git a/src/SoapCore/SoapEndpointMiddleware.cs b/src/SoapCore/SoapEndpointMiddleware.cs index fe3c182a..79093638 100644 --- a/src/SoapCore/SoapEndpointMiddleware.cs +++ b/src/SoapCore/SoapEndpointMiddleware.cs @@ -957,14 +957,24 @@ private async Task ProcessXSD(HttpContext httpContext) private async Task ProcessMetaFromFile(HttpContext httpContext, bool showDocumentation) { var meta = new MetaFromFile(); + + var url = httpContext.Request.Path.Value.Replace("/", string.Empty); + + WebServiceWSDLMapping mapping = _options.WsdlFileOptions.WebServiceWSDLMapping[url]; + if (!string.IsNullOrEmpty(_options.WsdlFileOptions.VirtualPath)) { meta.CurrentWebServer = _options.WsdlFileOptions.VirtualPath + "/"; } - meta.CurrentWebService = httpContext.Request.Path.Value.Replace("/", string.Empty); - - WebServiceWSDLMapping mapping = _options.WsdlFileOptions.WebServiceWSDLMapping[meta.CurrentWebService]; + if (string.IsNullOrEmpty(mapping.UrlOverride)) + { + meta.CurrentWebService = url; + } + else + { + meta.CurrentWebService = mapping.UrlOverride; + } meta.XsdFolder = mapping.SchemaFolder; meta.WSDLFolder = mapping.WSDLFolder; diff --git a/src/SoapCore/WSDLFileOptions.cs b/src/SoapCore/WSDLFileOptions.cs index 5da9fea9..502b3639 100644 --- a/src/SoapCore/WSDLFileOptions.cs +++ b/src/SoapCore/WSDLFileOptions.cs @@ -1,13 +1,18 @@ +using System; using System.Collections.Generic; -using SoapCore.Meta; namespace SoapCore { public class WsdlFileOptions { - public Dictionary WebServiceWSDLMapping { get; set; } + public virtual Dictionary WebServiceWSDLMapping { get; set; } = new Dictionary(); public string UrlOverride { get; set; } public string VirtualPath { get; set; } public string AppPath { get; set; } } + + public class WsdlFileOptionsCaseInsensitive : WsdlFileOptions + { + public override Dictionary WebServiceWSDLMapping { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + } } diff --git a/src/SoapCore/WebServiceWSDLMapping.cs b/src/SoapCore/WebServiceWSDLMapping.cs index 85021e6e..af5f2f22 100644 --- a/src/SoapCore/WebServiceWSDLMapping.cs +++ b/src/SoapCore/WebServiceWSDLMapping.cs @@ -4,10 +4,11 @@ namespace SoapCore { - public class WebServiceWSDLMapping - { - public string WsdlFile { get; set; } - public string WSDLFolder { get; set; } - public string SchemaFolder { get; set; } - } + public class WebServiceWSDLMapping + { + public string UrlOverride { get; set; } + public string WsdlFile { get; set; } + public string WSDLFolder { get; set; } + public string SchemaFolder { get; set; } + } }