XenoPsy
XenoPsy

Reputation: 103

C# WebClient StreamReader continuous read missing last line

Greetings good people,

I have a rather strange issue (for me at least) regarding WebClient and reading data for a continuous data stream and I’m not really sure where the issue is. The stream receives data almost as expected, except the last row. But when new data arrives, the unprinted row prints above the new data.

For example, a set of lines is retreived and it could look like this:

<batch name="home">
    <event id="1"/>

And when the next set arrives, it contains the missing end block from the above set:

</batch>
<batch name="home">
    <event id="2"/>

The code presented is simplified, but hopefully is enough for getting a clearer picture.

WebClient _client = new WebClient();

_client.OpenReadCompleted += (sender, args) =>
{
    using (var reader = new StreamReader(args.Result))
    {
        while (!reader.EndOfStream)
        {
            Console.WriteLine(reader.ReadLine());
        }
    }
};

_client.OpenReadAsync(new Uri("localhost:1234/testdata?keep=true"));

In this setup the reader.EndOfStream never gets to true because the stream doesn't end. Anyone have a suggestion on how to retrieve the last line? Am I missing something or could the fault be with the API?

Kind regards :)

Upvotes: 0

Views: 277

Answers (1)

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131180

It seems there's simply no newline character after the batch element. In XML whitespace, including newlines, isn't significant so no newlines are required. XML doesn't allow multiple root elements though, which makes this scenario a bit weird.

In streaming scenarios it's common to send each message unindented (ie in a single line) and send either a newline or another uncommon character to mark the end of the message. One would expect either no newlines at all, or a newline after each batch, eg :

<batch name="home"><event id="1"/>...</batch>
<batch name="home"><event id="2"/>...</batch>
<batch name="home"><event id="3"/>...</batch>

In that case you could use just a ReadLine to read each message:

var client=new HttpClient();
using var stream=client.GetStreamAsync(serviceUrl);

using var reader=new StreamReader(stream);
while(true)
{
    var msg=reader.ReadLine();
    var doc=XDocument.Parse(msg);
    ...
}

Without another way to identify each message though, you'll have to read each element form the stream. Luckily, LINQ-to-XML makes it a bit easier to read elements :

using var reader=XmlReader.Create(stream,new XmlReaderSettings{ConformanceLevel = ConformanceLevel.Fragment});
while (reader.Read())
{
    switch (reader.NodeType)
    {
        case XmlNodeType.Element:
            if (reader.Name == "batch") {
                    XElement el = XElement.ReadFrom(reader) as XElement;
                    //Process the batch!
            }
            break;
    }
}

Upvotes: 1

Related Questions