0

I am working with C#, WebAPI 2 and getting a weird behavior.

I have service that calls Controller and content can be sent sometimes as Json and others as XML mechanism that is used to make a call looks following:

using (var client = new HttpClient())
{
    var pricingControllerUrl = CreateEndpoint(apiUrl);
    using (var response = (int)request.Metadata.InputType >= 3 ?  client.PostAsJsonAsync(pricingControllerUrl, request) : client.PostAsXmlWithSerializerAsync(pricingControllerUrl, request))
    {
        if (response.Result.IsSuccessStatusCode)
        {
            var session = response.Result.Content.ReadAsAsync<Session>(new List<MediaTypeFormatter>() { new XmlMediaTypeFormatter { UseXmlSerializer = true }, new JsonMediaTypeFormatter() }).Result;
            return session;
        }
    }
}

public static class HttpExtensions
{
    public static Task<HttpResponseMessage> PostAsXmlWithSerializerAsync<T>(this HttpClient client, string requestUri, T value)
    {
        return client.PostAsync(new Uri(requestUri), value,
                      new XmlMediaTypeFormatter { UseXmlSerializer = true }
                      );
    }
}

On the receiving end (controller),

public async Task<IHttpActionResult> PostSession([FromBody] Session session)
    {
    //do the calculations
    return Content(HttpStatusCode.OK, sessionResponse, new ReducedSessionFormatter(), this.Request.Content.Headers.ContentType);
    }

response has to be reduced by removing some information just before it gets dispatched, formatter below is used to facilitate this:

public class ReducedSessionFormatter : MediaTypeFormatter
{
    public ReducedSessionFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
    }

    public ReducedSessionFormatter(MediaTypeFormatter formatter) : base(formatter)
    {
    }

    public override bool CanReadType(Type type)
    {
        return false;
    }

    public override bool CanWriteType(Type type)
    {
        return type.IsAssignableFrom(typeof (Session));
    }

    protected XDocument ReduceXml(XDocument doc)
    {

        //removing stuff from xml
        return doc;
    }

    protected JObject ReduceJson(JObject serializedJson)
    {
        //removing stuff from json
        return serializedJson;
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
    {
        if (content.Headers.ContentType.MediaType.Contains("xml"))
        {
            var doc = SerializeToXmlDocument(type, value).ToXDocument();
            doc = ReduceXml(doc);

            var settings = new XmlWriterSettings {Encoding = new UTF8Encoding(false)};
            using (XmlWriter w = XmlWriter.Create(writeStream, settings))
            {
                doc.Save(w);
            }
        }
        else
        {
            var json = new JavaScriptSerializer().Serialize(value);
            var serializedJson = (JObject)JsonConvert.DeserializeObject(json);
            var serializedJsonString = ReduceJson(serializedJson).ToString(Newtonsoft.Json.Formatting.None);
            var writer = new StreamWriter(writeStream);
            writer.Write(serializedJsonString);
        }

        var tcs = new TaskCompletionSource<object>();
        tcs.SetResult(null);
        return tcs.Task;
    }

    public XmlDocument SerializeToXmlDocument(Type type, object value)
    {
        var serializer = new XmlSerializer(type);

        XmlDocument xmlDocument = null;

        using (var memoryStream = new MemoryStream())
        {
            serializer.Serialize(memoryStream, value);
            memoryStream.Position = 0;
            using (var xtr = XmlReader.Create(memoryStream, new XmlReaderSettings {IgnoreWhitespace = true}))
            {
                xmlDocument = new XmlDocument();
                xmlDocument.Load(xtr);
            }
        }

        return xmlDocument;
    }
}

public static class XmlExtensions
{
    public static XDocument ToXDocument(this XmlDocument xmlDocument)
    {
        using (var nodeReader = new XmlNodeReader(xmlDocument))
        {
            nodeReader.MoveToContent();
            return XDocument.Load(nodeReader);
        }
    }
}
public static class JsonExtensions
{
    public static bool IsNullOrEmpty(this JToken token)
    {
        return (token == null) ||
               (token.Type == JTokenType.Array && !token.HasValues) ||
               (token.Type == JTokenType.Object && !token.HasValues) ||
               (token.Type == JTokenType.String && token.ToString() == String.Empty) ||
               (token.Type == JTokenType.Null);
    }
}

When aggregated in formatter both xml and json are valid and fine and if data is sent back without using formatter everything works

Weird stuff: when I am using formatter and send back Json it gets truncated independent from length of it. Even very tiny objects (less than 10k length) get cut off always at same place for same object but at different length for different objects, and only for json and work just fine for xml...

it also fails if json is not being toStringed like:

            var writer = new StreamWriter(writeStream);
            writer.Write(ReduceJson(serializedJson));

I have added minimal solution to show the issue

What is going on here? Why using formatter truncates response content for Json but not XML?

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
  • Not thinking thoroughly but would SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")) help? – Nathan Oct 19 '16 at 17:27
  • I know it's less than 10k but has it to do with your default ? – Nathan Oct 19 '16 at 17:34
  • Or maybe some escape characters are involved? – Nathan Oct 19 '16 at 17:37
  • Or just an FYI: http://stackoverflow.com/questions/13516844/windows-8-apparently-removes-content-encoding-header-from-compressed-http-respon – Nathan Oct 19 '16 at 17:39
  • @Nathan Hi Nathan, thanks for help `MediaTypeHeaderValue("application/json")` didn't do it... I have checked for escape characters and they do seem to be ok... I am now looking on how to length – Matas Vaitkevicius Oct 20 '16 at 10:28

1 Answers1

0

Ok I have found what the issue was it was usage of Task .Result; in

var session = response.Result.Content.ReadAsAsync<Session>(new List<MediaTypeFormatter>() { new XmlMediaTypeFormatter { UseXmlSerializer = true }, new JsonMediaTypeFormatter() }).Result;

(and some other mechanisms that are not listed in the question) that should have been awaited.

Once replaced .Results with await and marked all methods in chain as async - I started getting full responses.

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265