0

In SoapCore the HttpContext is replaced with a MemoryStream which is the snippet contained below, or can be found on GitHub.

// `HttpContext.Request.Body` comes in as a `FileBufferingReadStream`
private async Task ProcessOperation(HttpContext httpContext, IServiceProvider serviceProvider)
{
    Message responseMessage;

    //Reload the body to ensure we have the full message
    var memoryStream = new MemoryStream((int)httpContext.Request.ContentLength.GetValueOrDefault(1024));
    await httpContext.Request.Body.CopyToAsync(memoryStream).ConfigureAwait(false);
    memoryStream.Seek(0, SeekOrigin.Begin);
    httpContext.Request.Body = memoryStream;

    // ... Removed for brevity
}

I can't figure out how to convert the body into a string so I can use XPath to get some descendants:

using var reader = new StreamReader(httpContext.Request.Body);
// The `requestBody` is cutoff and appears to only be half of the request, which
// appears to be the issue
var requestBody = await reader.ReadToEndAsync();

// Throws on this line with `XmlException There are multiple root elements`, which
// based on the requestBody being clipped makes sense
var xDocument = XDocument.Parse(requestBody);
var query = $"//{prefix}:{bodyElement}";
return xDocument.XPathSelectElement(query, GetXmlNamespaceManager(prefix, uri));

Can anyone see why this doesn't work, or uses SoapCore to get the raw request body as a string instead of deserializing to models?

mtpultz
  • 17,267
  • 22
  • 122
  • 201
  • 1
    Maybe the issue is the position of the stream? Try setting it to the start of the stream by `httpContext.Request.Body.Seek(0, SeekOrigin.begin)` – H.Mikhaeljan Nov 20 '20 at 09:42
  • 1
    You're already reading that text. The error complains that the *text* contains invalid XML. What is the actual payload? It should be a valid XML file. You don't need to cache the data into memory with MemoryStream or as string with `ReadToEndAsync` either, `XDocument.Load(stream)` can parse a stream's contents directly. `XDocument.Load(httpContext.Request.Body)` should be enough – Panagiotis Kanavos Nov 20 '20 at 11:11
  • What are the *actual* contents? If the request is bad, if the client sent only part of the XML payload, it can't be parsed. Use a debugging proxy like Fiddler to see what is actually sent to the service – Panagiotis Kanavos Nov 20 '20 at 11:16
  • @PanagiotisKanavos I tested the request using some middleware, and I can use the exact same method and get the complete request from the incoming `HttpRequestStream`, but coming out of SoapCore I get only the bottom half assigned to `requestBody`, which throws the error when trying to use `XDocument.Parse`, which is why I identified it as the issue in the code comments. – mtpultz Nov 20 '20 at 15:14
  • @PanagiotisKanavos the `MemoryStream` is created in SoapCore. I'm not putting it into the `MemoryStream` I traced it back through their code to find out why it wasn't an `HttpRequestStream` like I would have expected, and now I'm trying to figure out how to get the request body. – mtpultz Nov 20 '20 at 15:17
  • Why would you try to read from a partially read stream? By definition you'll be in the middle of the document - if not the end. Any parsing code you try is guaranteed to fail. This isn't a matter of converting the stream to a string. The data was already consumed. You can't go back in a network stream and read from it again. If you want to parse the stream yourself you'll have to read from it first. – Panagiotis Kanavos Nov 20 '20 at 15:30
  • In fact, that's why SoapCore *copies* the contents into memory. When `CopyToAsync` completes the buffer (that's what a MemoryStream is) contains the entire string and the network stream is at its end. If you use any middleware that reads from the string *before* that, you're consuming parts of the XML payload that will never reach SoapCore and the XML Serializer – Panagiotis Kanavos Nov 20 '20 at 15:33
  • @PanagiotisKanavos reading the request myself was just a test using a bit of inlined middleware to make sure the request is valid. I have it commented out using the SoapCore middleware. – mtpultz Nov 20 '20 at 15:37
  • 1
    @H.Mikhaeljan thanks looks like using `httpContext.Request.Body.Seek(0, SeekOrigin.Begin)` worked – mtpultz Nov 20 '20 at 15:44
  • Post your code in the question. Or create a new, *minimal* example with no middleware, nothing but a simple no-op operation. The only way to get `XmlException There are multiple root elements` from a valid XML string is if you start reading it from the middle. Which means that something *outside* SoapCore already grabbed half the input – Panagiotis Kanavos Nov 20 '20 at 15:44
  • 1
    @PanagiotisKanavos thanks I also used `XDocument.Load(httpContext.Request.Body)` after rewinding the stream instead of using the `StreamReader` and `XDocument.Parse`. – mtpultz Nov 20 '20 at 15:45
  • @mtpultz that's surprising because neither NetworkStream nor FrameRequestStream support seeking. – Panagiotis Kanavos Nov 20 '20 at 15:47
  • Did you call `EnableRewind()` on the context? This buffers the request body, the same way copying to `MemoryStream` does. This works for small SOAP messages. It will cause problems if large SOAP messages are sent – Panagiotis Kanavos Nov 20 '20 at 15:52
  • Sorry @PanagiotisKanavos I don't know why it works. I'm far from an expert in C# at the moment coming from PHP, but this seems to work. I have the entire request body including and the XPath query strips off the envelope and body. – mtpultz Nov 20 '20 at 15:56
  • @PanagiotisKanavos no `EnableRewind()` wasn't available – mtpultz Nov 20 '20 at 15:56
  • 1
    It *is* available, and used by SoapCore in the page you linked. You haven't posted anything that could demonstrate the problem or even explain what you tried. Somewhere, something is reading half the stream. I'd bet SoapCore wouldn't leave a stream half-read after 2 years of development, so I'd guess that either your own code/middleware or some third party code forgets to rewind the stream. Or your own code is trying to read from the stream while SoapCore is still using it. Even with rewinding, you're doing what SoapCore does, deserialize the entire body. This is *very* expensive – Panagiotis Kanavos Nov 20 '20 at 16:04
  • @PanagiotisKanavos looks like `EnableBuffering()` is available and it calls the `BufferingHelper.EnableRewind(request)`. Are you indicating that I shouldn't be able to use `Seek` until I `EnableRewind`? – mtpultz Nov 20 '20 at 16:05
  • Thanks @PanagiotisKanavos I'll look into our other middleware since we don't have any custom middleware other than what I inlined as a quick test. – mtpultz Nov 20 '20 at 16:06
  • 1
    This question was closed as a duplicate that shows that you need `EnableBuffering()` to be able to rewind. [This blog post](https://gunnarpeipman.com/aspnet-core-request-body/) goes into greater detail. You can't enable buffering *after* some data is read and gone though, which suggests SoapCore enabled this. You'll have to ensure your own middleware rewinds after processing too – Panagiotis Kanavos Nov 20 '20 at 16:14
  • @PanagiotisKanavos it's actually a work around for a couple SOAP endpoints where we don't want to maintain a set of models for deserializing, and instead want to grab a single bit of deeply nested data quickly – mtpultz Nov 20 '20 at 16:14
  • 1
    @mtpultz I've faced similar problems too, although in my case it was an airline's large SOAP response that didn't respect its own XSD. XDocument fully deserializes the payload though and produces a graph of XNode objects. If you want to avoid that you'll have to use XmlReader and read elements in a loop until you find the ones you want. – Panagiotis Kanavos Nov 20 '20 at 16:17
  • wel glad it worked out ^^ – H.Mikhaeljan Nov 21 '20 at 20:44

0 Answers0