I need to build a service capable of receiving SOAP messages and re-route those to a REST microservice.
The message sender cannot be modified but my service can (and it must I guess): I decided to build an ASP.NET ASMX WebService because it seemed the most straightforward solution to handle SOAP messages;
With that said I'm facing a specific issue during my development and my researches couldn't help me to solve it (please consider that I started my career as a developer not more than 5 years ago so my knowledge over SOAP webservices in dotnet is pretty insufficient):
The object I'll be receiving will have the following structure:
<s:Envelope xmlns:a="http://<sender-website-containing-this-schema>/soap/encoding"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="<SOME_STRING_WITH_THIS_FORMAT>">
<s:Body>
<ns1:EndpointRelatedFunction>
<data></data>
...
The webservice I wrote till now is not that different from what VisualStudio generates when adding a new asmx file (I'll report it for completeness):
namespace ASMXWebService
{
/// <summary>
/// Summary description for ProxySOAP
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class ASMXWebService : WebService
{
[WebMethod]
public string EndpointRelatedFunction(object data)
{
return "Hello World";
}
}
}
This portion of code generates an enpoint that can receive xmls messages as the one that follows:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<EndpointRelatedFunction xmlns="http://tempuri.org/">
<data />
</EndpointRelatedFunction>
</soap:Body>
</soap:Envelope>
My problem is that when I try to send the object that this service will have to receive (I'm using postman) I get the an "500 Internal server error" with the following error message:
soap:VersionMismatchSystem.Web.Services.Protocols.SoapException: Possible SOAP version mismatch:
Envelope namespace <http://<sender-website-containing-this-schema>/soap/encoding> was
unexpected. Expecting http://schemas.xmlsoap.org/soap/envelope/. at
System.Web.Services.Protocols.SoapServerProtocol.CheckHelperVersion() at
System.Web.Services.Protocols.SoapServerProtocol.Initialize() at
System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context,
HttpRequest request, HttpResponse response, Boolean& abortProcessing)
I guess I should add something on the top of
public class ASMXWebService : WebService
but I have no idea what it would be.
Solving this issue might solve my other problems (I'm guessing will be my next problems) and those might be:
I'm open to redefine the whole structure of my service (anything but having to use WCF, if it's the best way tell me why and show me how please).
UPDATE:
Apparently as long as the namespace URI is reachable the webservice is able parse the xml correctly without having to add anything to the ASMXWebService.asmx.cs file;
So that error was likely related to the fact that on the network I was I couldn't reach the first namespace's URI.
The problem I'm facing now is related to the "ns1" namespace, it does not seem an URI so I've contacted the sender service maintainers to get infos on whatever that is.
UPDATE 2:
Apparently the ns1 namespace was just what "http://tempuri.org/" was in my ASMXWebService.asmx.cs file; Changing "http://tempuri.org/" to ns1 namespace does still give an error because the webservice is not able to deserialize the xml body (is expecting
<EndpointRelatedFunction xlmns="WEB_SERVICE_NAMESPACE_THAT_PREVIOUSLY_WAS_TEMPURI">
insted of <ns1:EndpointRelatedFunction>
).
I'm trying to solve the problem by intercepting the xml message before it gets deserialized and change it in order to work with the xml schema that my webservice is using (a bit odd considering that those schemas were given by sender's maintainers so the example they provided me should have been generated by that same xml schema).
To do so I'm using SoapExtension (https://learn.microsoft.com/en-us/dotnet/api/system.web.services.protocols.soapextension?view=netframework-4.8.1).
If you need to build a service in dotnet that can interact with both SOAP and REST services you can follow these steps:
using Microsoft.AspNetCore.Mvc;
using System.Xml.Serialization;
namespace ProxySOAPWebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrderRequest : ControllerBase
{
[HttpPost(Name = "OrderRequest")]
[Consumes("text/xml")]
public async Task<IActionResult> OrderRequestPost()
{
string content = await new StreamReader(Request.Body).ReadToEndAsync();
// Open tag that I know will contain the data
string openTag = "<ns1:EndpoinRelatedFunction>";
// Closing tag that I know will contain the data
string closeTag = "</ns1:EndpoinRelatedFunction>";
int start = content.IndexOf(openTag);
int end = content.IndexOf(closeTag) + closeTag.Length;
//Get only the content inside the open and end tag
string processedContent = content.Substring(start, end - start);
//Remove unwanted 'ns1:' namespace
string readyToParseContent = processedContent.Replace("ns1:","");
//Deserialize the sanitized xml string
DataStructure? orderRequest;
XmlSerializer serializer = new XmlSerializer(typeof(DataStructure));
using (StringReader reader = new StringReader(readyToParseContent))
{
orderRequest = serializer.Deserialize(reader) as DataStructure;
}
return Ok($"Done: {orderRequest?.dataStructureProperty1}, {orderRequest?.dataStructureProperty2}");
}
}
}
This is the most straightforward way I've found to have a dotnet service that is able to handle both SOAP and REST requests.
The provided solution is using dotnet core so it's not using third party libraries as SOAP-Core or old and dismissed frameworks (as dotnet framework).
After trying with SoapExtension I couldn't intercept the xml message and edit it before the WebService could parse it, the documentation is a bit patchy in my opinion (especially on how to provide the modified xml string to the WebService).
In the end I've decided to approach the problem with a bottom up technique:
considering that, in the end, SOAP requests are just HTTP POST (and GET) with an envelope containing the actual data and an extra header (that specifies the 'SOAPAction') I decided to ignore SOAP directives and parse the body myself;
so I've built a dotnet core web api service and designed a POST endpoint that consumes 'text/xml' (could be anything but I've explicitly defined that to make it clear what the endpoint is supposed to receive) and treat the body as a raw string to first remove all the tags that the ASMX service could not parse and adapt it to what an XML Serializer was able to deserialize.
I've provided the example in the TL;TR section.