1

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:

sender-example.xml
<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):

ASMXWebService.asmx.cs
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:

  • How can I define those custom prefix namespaces (like ':a' or ':ns1') in the asmx code?
  • What could be the value for xmlns:ns1 with that strange format? (I would expect only paths as namespace value).

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).

6
  • Try commenting out the namespace and see what happens : [WebService(Namespace = "tempuri.org/")] The namespace is on a child tag and not the entire envelope. Commented Jan 21 at 13:16
  • Apparently as long as the URI of the namespace is reachable the webservice is able to parse the xml correctly; right now I'm having an issue with the namespace "ns1", I've contacted the developers responsible for the sender and as soon as I'll find out what the string in that weird format (used as URI for the namespace "ns1") is and what is related to I'll let you know. Commented Jan 22 at 8:10
  • 1
    ns1 is a default namespace prefix. The URL is for you to create a custom schema locally. if you do not want to validate the XML with a local schema than eliminate the prefix and the URL. Or create a local schema file. Commented Jan 22 at 17:12
  • So the ns1 namespace was just the same thing that tempuri.org is in my ASMXWebService.asmx.cs file. Commented Jan 23 at 12:52
  • It could be if the XML meets the tempuri.org specifications. Commented Jan 23 at 13:00

1 Answer 1

0

TL;DR

If you need to build a service in dotnet that can interact with both SOAP and REST services you can follow these steps:

  1. Open visual studio and create a new "ASP.NET Core Web Api" project.
  2. Create a controller inside "Controllers" folder and paste with the following code:

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}");
        }
    }
}

  1. Create and edit DataStructure to match what the sender is actually sending to you, if you have the xml schema you could as well use xsd (https://learn.microsoft.com/en-us/dotnet/standard/serialization/xml-schema-definition-tool-xsd-exe) command to automatically create a cs class that will follow exactly the structure defined by the sender in the schema.
  2. Now the sender service will be able to send SOAP requests to POST https:///api/OrderRequest/ with whatever header and whatever body envelope he has.

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).

END TL;DR

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.