Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/SoapCore.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SoapCore;
using SoapCore.Extensibility;
using SoapCore.ServiceModel;
using SoapCore.Tests.Model;
using SoapCore.Tests.Utilities;

Expand Down Expand Up @@ -359,6 +368,103 @@ public async Task HandleConcurrentCallsCorrectly()
Assert.AreEqual("hello, async", r[1]);
}

[TestMethod]
[DataRow(false, true)]

// This case is to document behavior. If we fix the root cause of this issue, we should be able to change this expected result to true.
[DataRow(true, false)]
public async Task ModifyingCustomMessageNamespaceManagerDoesNotAffectOtherRequestsWithNamespaceManagerReuseDisabled(bool reuseXmlNamespaceManager, bool expectEquality)
{
var namespacePrefixOverrides = new XmlNamespaceManager(new NameTable());
namespacePrefixOverrides.AddNamespace("s", XmlSchema.Namespace);
namespacePrefixOverrides.AddNamespace("soap12", Namespaces.SOAP12_NS);
namespacePrefixOverrides.AddNamespace("soap", Namespaces.SOAP11_ENVELOPE_NS);
namespacePrefixOverrides.AddNamespace("wsdl", Namespaces.WSDL_NS);

using var host = CreateNamespaceIsolationTestHost(namespacePrefixOverrides, reuseXmlNamespaceManager: reuseXmlNamespaceManager);
using var httpClient = host.CreateClient();

var preWsdl = await LoadWsdlAsync(httpClient);
Assert.IsTrue(preWsdl.Contains("<s:schema"), "Expected the s prefix override on the root schema element.");

await SendSoap12AsyncMethodAsync(host);

var postWsdl = await LoadWsdlAsync(httpClient);
if (expectEquality)
{
Assert.AreEqual(preWsdl, postWsdl);
}
else
{
Assert.AreNotEqual(preWsdl, postWsdl);
}
}

private static TestServer CreateNamespaceIsolationTestHost(XmlNamespaceManager namespacePrefixOverrides, bool reuseXmlNamespaceManager)
{
var webHostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddRouting();
services.AddSoapCore();
services.TryAddSingleton<TestService>();
services.AddSoapMessageInspector<TestMessageInspectorThatModifiesNamespace>();
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<TestService>(opt =>
{
opt.Path = "/Service.asmx";
opt.SoapSerializer = SoapSerializer.XmlSerializer;
opt.EncoderOptions = new[]
{
new SoapEncoderOptions { MessageVersion = MessageVersion.Soap11 },
new SoapEncoderOptions { MessageVersion = MessageVersion.Soap12WSAddressing10 },
};
opt.XmlNamespacePrefixOverrides = namespacePrefixOverrides;
opt.ReuseXmlNamespaceManager = reuseXmlNamespaceManager;
});
});
});

return new TestServer(webHostBuilder);
}

private static async Task SendSoap12AsyncMethodAsync(TestServer host)
{
const string body = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap:Envelope xmlns:soap=""http://www.w3.org/2003/05/soap-envelope"">
<soap:Body>
<AsyncMethod xmlns=""http://tempuri.org/"" />
</soap:Body>
</soap:Envelope>";

using var content = new StringContent(body, Encoding.UTF8, "application/soap+xml");
using var response = await host
.CreateRequest("/Service.asmx")
.AddHeader("SOAPAction", @"""http://tempuri.org/ITestService/AsyncMethod""")
.And(msg => msg.Content = content)
.PostAsync();

response.EnsureSuccessStatusCode();
}

private static async Task<string> LoadWsdlAsync(HttpClient httpClient)
{
var wsdlResponse = await httpClient.GetAsync("/Service.asmx?wsdl");
if (wsdlResponse.IsSuccessStatusCode)
{
return await wsdlResponse.Content.ReadAsStringAsync();
}

var content = await wsdlResponse.Content.ReadAsStringAsync();
Assert.Fail($"Failed to load wsdl, status code: {wsdlResponse.StatusCode}, content: {content}");
throw new InvalidOperationException("Unreachable code");
}

private ITestService CreateClient(bool caseInsensitivePath = false)
{
var binding = new BasicHttpBinding();
Expand Down Expand Up @@ -400,5 +506,21 @@ private ITestService CreateSoap11Iso88591Client()
var serviceClient = channelFactory.CreateChannel();
return serviceClient;
}

private class TestMessageInspectorThatModifiesNamespace : IMessageInspector2
{
public object AfterReceiveRequest(ref Message message, ServiceDescription serviceDescription)
{
return message;
}

public void BeforeSendReply(ref Message reply, ServiceDescription serviceDescription, object correlationState)
{
if (reply is CustomMessage msg)
{
msg.XmlNamespaceLookup.AddNamespace("xsd", XmlSchema.Namespace);
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/SoapCore/SoapCoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ public class SoapCoreOptions
/// </summary>
public bool UseLegacyWsdlNaming { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether to reuse the same XmlNamespaceManager for all requests using the same binding and encoding, or create a new one for each request.
/// Reusing the same one can improve performance, but may cause issues if you modify the namespace manager in your code. Defaults to true.
/// </summary>
public bool ReuseXmlNamespaceManager { get; set; } = true;

public void UseCustomSerializer<TCustomSerializer>()
where TCustomSerializer : class, IXmlSerializationHandler
{
Expand Down
5 changes: 5 additions & 0 deletions src/SoapCore/SoapEndpointMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,11 @@ private async Task ProcessMetaFromFile(HttpContext httpContext, bool showDocumen

private ConcurrentXmlNamespaceLookup GetXmlNamespaceLookup(SoapMessageEncoder messageEncoder)
{
if (!_options.ReuseXmlNamespaceManager)
{
return CreateDefaultNamespaceManager(messageEncoder);
}

return _xmlNamespaceLookupsByMessageEncoder.GetOrAdd(messageEncoder?.ToString() ?? "no_encoder", _ => CreateDefaultNamespaceManager(messageEncoder));

ConcurrentXmlNamespaceLookup CreateDefaultNamespaceManager(SoapMessageEncoder messageEncoder)
Expand Down
7 changes: 7 additions & 0 deletions src/SoapCore/SoapOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public class SoapOptions

public bool UseMicrosoftGuid { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether to reuse the same XmlNamespaceManager for all requests using the same binding and encoding, or create a new one for each request.
/// Reusing the same one can improve performance, but may cause issues if you modify the namespace manager in your code. Defaults to true.
/// </summary>
public bool ReuseXmlNamespaceManager { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether to check to make sure that the XmlOutput doesn't contain invalid characters
/// <para>Defaults to true</para>
Expand Down Expand Up @@ -111,6 +117,7 @@ public static SoapOptions FromSoapCoreOptions(SoapCoreOptions opt, Type serviceT
SchemeOverride = opt.SchemeOverride,
XmlIgnoreOnlyForWsdl = opt.XmlIgnoreOnlyForWsdl,
UseLegacyWsdlNaming = opt.UseLegacyWsdlNaming,
ReuseXmlNamespaceManager = opt.ReuseXmlNamespaceManager,
};

return options;
Expand Down
Loading