Skip to content

Commit

Permalink
Merge pull request #285 from Simon-Campbell/wsa-10-response
Browse files Browse the repository at this point in the history
Basic SOAP 1.2 + WSA 1.0 response (MessageID correlation)
  • Loading branch information
kotovaleksandr authored Jul 16, 2019
2 parents dbbcb98 + 14e564a commit 22141e6
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 39 deletions.
160 changes: 130 additions & 30 deletions src/SoapCore.Tests/RawRequestSoap12Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,109 @@ public void Soap12PingNoActionInHeader()
}
}

[TestMethod]
public async Task Soap12Wsa10Header()
{
var requestBody = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"" xmlns:wsa=""http://www.w3.org/2005/08/addressing"">
<soap12:Header>
<wsa:MessageID>uuid:7673868d-231e-490d-9c4f-19288e7e668d</wsa:MessageID>
<wsa:To>http://wsa10example</wsa:To>
<wsa:ReplyTo>
<wsa:Address>http://business456.example/client1</wsa:Address>
</wsa:ReplyTo>
<wsa:Action>Ping</wsa:Action>
</soap12:Header>
<soap12:Body>
<Ping xmlns=""http://tempuri.org/"">
<s>abc</s>
</Ping>
</soap12:Body>
</soap12:Envelope>
";

using (var host = CreateTestHost())
using (var client = host.CreateClient())
using (var content = new StringContent(requestBody, Encoding.UTF8, "application/soap+xml"))
using (var response = await host.CreateRequest("/WSA10Service.svc").And(msg => msg.Content = content).PostAsync())
{
response.EnsureSuccessStatusCode();

var responseBodyStream = await response.Content.ReadAsStreamAsync();
var document = XDocument.Load(responseBodyStream);

Assert.IsNotNull(document, "The XML should be properly formed");

var headerElement = document
.Element(XName.Get("Envelope", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Header", "http://www.w3.org/2003/05/soap-envelope"));

Assert.IsNotNull(headerElement, "There should be a SOAP header");

var header = new
{
Action = headerElement.Element(XName.Get("Action", "http://www.w3.org/2005/08/addressing"))?.Value,
RelatesTo = headerElement.Element(XName.Get("RelatesTo", "http://www.w3.org/2005/08/addressing"))?.Value,
To = headerElement.Element(XName.Get("To", "http://www.w3.org/2005/08/addressing"))?.Value,
};

header.ShouldDeepEqual(new
{
Action = "http://tempuri.org/ITestService/PingResponse",
RelatesTo = "uuid:7673868d-231e-490d-9c4f-19288e7e668d",
To = "http://business456.example/client1"
});
}
}

[TestMethod]
public async Task Soap12Wsa10FaultHeader()
{
var requestBody = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"" xmlns:wsa=""http://www.w3.org/2005/08/addressing"">
<soap12:Header>
<wsa:MessageID>uuid:7673868d-231e-490d-9c4f-19288e7e668e</wsa:MessageID>
<wsa:To>http://wsa10example</wsa:To>
<wsa:Action>ThrowDetailedFault</wsa:Action>
</soap12:Header>
<soap12:Body>
<ThrowDetailedFault xmlns=""http://tempuri.org/"">
<detailMessage>Detail message</detailMessage>
</ThrowDetailedFault>
</soap12:Body>
</soap12:Envelope>
";

using (var host = CreateTestHost())
using (var client = host.CreateClient())
using (var content = new StringContent(requestBody, Encoding.UTF8, "application/soap+xml"))
using (var response = await host.CreateRequest("/WSA10Service.svc").And(msg => msg.Content = content).PostAsync())
{
Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);

var responseBodyStream = await response.Content.ReadAsStreamAsync();
var document = XDocument.Load(responseBodyStream);

Assert.IsNotNull(document, "The XML should be properly formed");

var headerElement = document
.Element(XName.Get("Envelope", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Header", "http://www.w3.org/2003/05/soap-envelope"));

Assert.IsNotNull(headerElement, "There should be a SOAP header");

var header = new
{
RelatesTo = headerElement.Element(XName.Get("RelatesTo", "http://www.w3.org/2005/08/addressing"))?.Value
};

header.ShouldDeepEqual(new
{
RelatesTo = "uuid:7673868d-231e-490d-9c4f-19288e7e668e"
});
}
}

[TestMethod]
public async Task Soap12DetailedFault()
{
Expand All @@ -82,49 +185,46 @@ public async Task Soap12DetailedFault()
</soap12:Body>
</soap12:Envelope>
";
var bodyBytes = Encoding.UTF8.GetBytes(body);
using (var host = CreateTestHost())
using (var client = host.CreateClient())
using (var content = new StringContent(body, Encoding.UTF8, "application/soap+xml"))
using (var response = await host.CreateRequest("/Service.svc").And(msg => msg.Content = content).PostAsync())
{
using (var response = host.CreateRequest("/Service.svc").And(msg => msg.Content = content).PostAsync().Result)
{
Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);

var responseBodyStream = await response.Content.ReadAsStreamAsync();
var document = XDocument.Load(responseBodyStream);
var responseBodyStream = await response.Content.ReadAsStreamAsync();
var document = XDocument.Load(responseBodyStream);

var faultElement = document
.Element(XName.Get("Envelope", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Body", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Fault", "http://www.w3.org/2003/05/soap-envelope"));
var faultElement = document
.Element(XName.Get("Envelope", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Body", "http://www.w3.org/2003/05/soap-envelope"))
.Element(XName.Get("Fault", "http://www.w3.org/2003/05/soap-envelope"));

var codeElement =
faultElement.Element(XName.Get("Code", "http://www.w3.org/2003/05/soap-envelope"));
var codeElement =
faultElement.Element(XName.Get("Code", "http://www.w3.org/2003/05/soap-envelope"));

Assert.IsNotNull(codeElement);
Assert.AreEqual("s:Sender", codeElement.Value);
Assert.IsNotNull(codeElement);
Assert.AreEqual("s:Sender", codeElement.Value);

var reasonElementText =
faultElement
.Element(XName.Get("Reason", "http://www.w3.org/2003/05/soap-envelope"))
.Elements(XName.Get("Text", "http://www.w3.org/2003/05/soap-envelope"));
var reasonElementText =
faultElement
.Element(XName.Get("Reason", "http://www.w3.org/2003/05/soap-envelope"))
.Elements(XName.Get("Text", "http://www.w3.org/2003/05/soap-envelope"));

Assert.IsNotNull(reasonElementText);
Assert.AreEqual(1, reasonElementText.Count());
Assert.AreEqual("test", reasonElementText.First().Value);
Assert.IsNotNull(reasonElementText);
Assert.AreEqual(1, reasonElementText.Count());
Assert.AreEqual("test", reasonElementText.First().Value);

var detailElement =
faultElement.Element(XName.Get("Detail", "http://www.w3.org/2003/05/soap-envelope"));
var detailElement =
faultElement.Element(XName.Get("Detail", "http://www.w3.org/2003/05/soap-envelope"));

Assert.IsNotNull(detailElement);
var faultDetail = detailElement.DeserializeInnerElementAs<FaultDetail>();
Assert.IsNotNull(detailElement);
var faultDetail = detailElement.DeserializeInnerElementAs<FaultDetail>();

faultDetail.ShouldDeepEqual(new FaultDetail
{
ExceptionProperty = "Detail message"
});
}
faultDetail.ShouldDeepEqual(new FaultDetail
{
ExceptionProperty = "Detail message"
});
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/SoapCore.Tests/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
app2.UseSoapEndpoint<TestService>("/Service.asmx", new BasicHttpBinding(), SoapSerializer.XmlSerializer);
});

app.UseWhen(ctx => ctx.Request.Path.Value.Contains("/WSA10Service.svc"), app2 =>
{
var transportBinding = new HttpTransportBindingElement();
var textEncodingBinding = new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressing10, System.Text.Encoding.UTF8);

app.UseSoapEndpoint<TestService>("/WSA10Service.svc", new CustomBinding(transportBinding, textEncodingBinding), SoapSerializer.DataContractSerializer);
});

app.UseMvc();
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/SoapCore/OperationDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe
Name = contractAttribute.Name ?? GetNameByAction(contractAttribute.Action) ?? GetNameByMethod(operationMethod);
SoapAction = contractAttribute.Action ?? $"{contract.Namespace.TrimEnd('/')}/{contract.Name}/{Name}";
IsOneWay = contractAttribute.IsOneWay;
ReplyAction = contractAttribute.ReplyAction;
DispatchMethod = operationMethod;

var returnType = operationMethod.ReturnType;
Expand Down Expand Up @@ -51,6 +50,8 @@ public OperationDescription(ContractDescription contract, MethodInfo operationMe
ReturnElementName = elementAttribute?.ElementName;
ReturnNamespace = elementAttribute?.Form == XmlSchemaForm.Unqualified ? string.Empty : elementAttribute?.Namespace;

ReplyAction = contractAttribute.ReplyAction ?? $"{Contract.Namespace.TrimEnd('/')}/{contract.Name}/{Name + "Response"}";

var faultContractAttributes = operationMethod.GetCustomAttributes<FaultContractAttribute>();
Faults = faultContractAttributes
.Where(a => a.DetailType?.Name != null)
Expand Down
2 changes: 1 addition & 1 deletion src/SoapCore/SoapCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Expand Down
40 changes: 33 additions & 7 deletions src/SoapCore/SoapEndpointMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
}
catch (Exception ex)
{
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, httpContext);
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, requestMessage, httpContext);
return responseMessage;
}

Expand All @@ -258,7 +258,7 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
}
catch (Exception ex)
{
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, httpContext);
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, requestMessage, httpContext);
return responseMessage;
}

Expand All @@ -275,7 +275,7 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
}
catch (Exception ex)
{
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, httpContext);
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, requestMessage, httpContext);
return responseMessage;
}

Expand Down Expand Up @@ -353,8 +353,21 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
// Create response message
var resultName = operation.ReturnName;
var bodyWriter = new ServiceBodyWriter(_serializer, operation, resultName, responseObject, resultOutDictionary);
responseMessage = Message.CreateMessage(messageEncoder.MessageVersion, null, bodyWriter);
responseMessage = new CustomMessage(responseMessage);

if (messageEncoder.MessageVersion.Addressing == AddressingVersion.WSAddressing10)
{
responseMessage = Message.CreateMessage(messageEncoder.MessageVersion, soapAction, bodyWriter);
responseMessage = new CustomMessage(responseMessage);

responseMessage.Headers.Action = operation.ReplyAction;
responseMessage.Headers.RelatesTo = requestMessage.Headers.MessageId;
responseMessage.Headers.To = requestMessage.Headers.ReplyTo?.Uri;
}
else
{
responseMessage = Message.CreateMessage(messageEncoder.MessageVersion, null, bodyWriter);
responseMessage = new CustomMessage(responseMessage);
}

httpContext.Response.ContentType = httpContext.Request.ContentType;
httpContext.Response.Headers["SOAPAction"] = responseMessage.Headers.Action;
Expand All @@ -373,7 +386,7 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
}

_logger.LogWarning(0, exception, exception.Message);
responseMessage = WriteErrorResponseMessage(exception, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, httpContext);
responseMessage = WriteErrorResponseMessage(exception, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, requestMessage, httpContext);
}
}

Expand All @@ -392,7 +405,7 @@ private async Task<Message> ProcessOperation(HttpContext httpContext, IServicePr
}
catch (Exception ex)
{
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, httpContext);
responseMessage = WriteErrorResponseMessage(ex, StatusCodes.Status500InternalServerError, serviceProvider, messageEncoder, requestMessage, httpContext);
return responseMessage;
}

Expand Down Expand Up @@ -649,6 +662,9 @@ private object DeserializeObject(System.Xml.XmlDictionaryReader xmlReader, Type
/// <param name="messageEncoder">
/// The Message Encoder.
/// </param>
/// <param name="requestMessage">
/// The Message for the incoming request
/// </param>
/// <param name="httpContext">
/// The HTTP context that received the response message.
/// </param>
Expand All @@ -661,6 +677,7 @@ private Message WriteErrorResponseMessage(
int statusCode,
IServiceProvider serviceProvider,
MessageEncoder messageEncoder,
Message requestMessage,
HttpContext httpContext)
{
var faultExceptionTransformer = serviceProvider.GetRequiredService<IFaultExceptionTransformer>();
Expand All @@ -669,6 +686,15 @@ private Message WriteErrorResponseMessage(
httpContext.Response.ContentType = httpContext.Request.ContentType;
httpContext.Response.Headers["SOAPAction"] = faultMessage.Headers.Action;
httpContext.Response.StatusCode = statusCode;

if (messageEncoder.MessageVersion.Addressing == AddressingVersion.WSAddressing10)
{
// TODO: Some additional work needs to be done in order to support setting the action. Simply setting it to
// "http://www.w3.org/2005/08/addressing/fault" will cause the WCF Client to not be able to figure out the type
faultMessage.Headers.RelatesTo = requestMessage.Headers.MessageId;
faultMessage.Headers.To = requestMessage.Headers.ReplyTo?.Uri;
}

messageEncoder.WriteMessage(faultMessage, httpContext.Response.Body);

return faultMessage;
Expand Down

0 comments on commit 22141e6

Please sign in to comment.