Question

I am calling a web service. It accepts Http Post. It is simple XML over Http. You send it an XML request document in the body of the request, you get back an XML response document in the body of the response. I have a nice library in our code base that that we've built over the years that does strongly typed serialization for both the request and response documents. Everything normally works fine.

Now, the new service I'm integrating with doesn't always send back the same object type in the response. Under certain error conditions it returns a special error document. Of course, in these situations my deserialization fails and the response data is lost. I know that I have a deserialization errror, but since the response is lost, I don't know what the root cause was.

I think the problem is that the stream returned by GetResponseStream() does not support seek operations, so when I get an error I cannot simply rewind the stream and re-read the data and handle it differently.

I'm looking for strategies to better handle this.

What I think I'm going to do is copy the response stream to a memory stream which is seekable before I attempt to deserialize. Then, if an error occurs I can rewind the memory stream and handle the response data differently. There was a good example in https://stackoverflow.com/a/3434077/90236.

Is there a better way to do this? It seems kind of wasteful to copy the response stream to a memory stream.

Simplified versioin of original code:

AccountRequest requestVal = new AccountRequest();
// initialize requestVal object

var request = (HttpWebRequest)WebRequest.Create("http://example.com/service");
request.Method = "POST";
request.ContentType = "text/xml";

using (Stream webStream = request.GetRequestStream())
{
    var serializer = new XmlSerializer(typeof(AccountRequest));
    serializer.Serialize(webStream, requestVal);
    webStream.Flush();
    webStream.Close();
}

AccountResponse returnVal;
using (var response = (HttpWebResponse)request.GetResponse())
{
    Stream responseStream = response.GetResponseStream();

    var serializer = new XmlSerializer(typeof(responseStream));

    try
    {
        returnVal = (AccountResponse)serializer.Deserialize(responseStream);
    }
    catch (Exception ex)
    {
      // if an exception occurs, the response stream data is lost. 
      // The responseStream is not seekable.
      logger.ErrorFormat("After Exception:\n{0}", ex.ToString());
      throw;
    }
}

Simplified version of proposed code:

AccountRequest requestVal = new AccountRequest();
// initialize requestVal object

var request = (HttpWebRequest)WebRequest.Create("http://example.com/service");
request.Method = "POST";
request.ContentType = "text/xml";

using (Stream webStream = request.GetRequestStream())
{
    var serializer = new XmlSerializer(typeof(AccountRequest));
    serializer.Serialize(webStream, requestVal);
    webStream.Flush();
    webStream.Close();
}

AccountResponse returnVal;
using (var response = (HttpWebResponse)request.GetResponse())
{
    Stream responseStream = response.GetResponseStream();
    using (MemoryStream ms = new MemoryStream())
    {    
      // copy response stream to a seekable memorystream
      int count = 0;
      do
      {
          byte[] buf = new byte[1024];
          count = responseStream.Read(buf, 0, 1024);
          ms.Write(buf, 0, count);
      } while (responseStream.CanRead && count > 0);    
      ms.Position = 0;

      // now attempt to desrialize from the memory stream
      var serializer = new XmlSerializer(typeof(AccountResponse));

      try
      {
          returnVal = (AccountResponse)serializer.Deserialize(ms);
      }
      catch (Exception ex)
      {
        // if an exception occured, rewind the stream and write an error to the log
        ms.Position = 0;
        using (var reader = new StreamReader(ms, Encoding.UTF8))
        {
            logger.ErrorFormat("After Exception:\n{0}\n\nRespons:\n{1}", 
                  ex.ToString(), reader.ReadToEnd());
        }
        throw;
      }
    }
}
Was it helpful?

Solution

If the returned error XML has a different root element you can use the XmlSerializer.CanDeserialize method before actually deserializing.

OTHER TIPS

Instead of copying the stream, what you could do is to read the whole stream as a string first, and then deserialize/ write logs as required. Below are code snippets:

 public string RetrieveResponse()
        {

            //Create a new http request
            HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create("http://example.com/service"); ;

            Stream responseStream = null;
            try
            {

                using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
                {
                    responseStream = httpResponse.GetResponseStream();

                    if (responseStream == null)
                    {
                        return null;
                    }

                    using (var streamRdr = new StreamReader(responseStream))
                    {
                        var response = streamRdr.ReadToEnd();

                        httpResponse.Close();

                        return response;
                    }
                }

            }
            finally
            {
                if (responseStream != null)
                {
                    responseStream.Dispose();
                }


            }
        }

Next, you can deserialize using the response returned by the snippet below:

private  AccountResponse LoadFromString(string response)
        {
            if (string.IsNullOrEmpty(response))
                return null;


            try
            {
                AccountResponse result = null;
                using (var stringReader = new StringReader(response))
                {
                    using (XmlReader reader = new XmlTextReader(stringReader))
                    {
                        var serializer = new XmlSerializer(typeof(AccountResponse));
                        result = serializer.Deserialize(reader) as AccountResponse;
                    }
                }

                return result;
            }
            catch (Exception exception)
            {
                //Log the exception, etc.

            }

        }

PS: if you know the format of the error returned by the service, you are actually able to deserialize the error to an object as well in one go, so we don't have to try and catch. Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top