如何使用来自C#的RestSharp RestClient发送组分隔符(不可打印的ascii字符)

时间:2016-11-08 12:15:15

标签: c# serialization json.net restsharp

更新

我已完全重写了这个问题并包含了一个完整的工作示例。

我现在的简洁问题是: 为什么我没有看到字符串" \ u001d"的个别字符?当我使用RestSharp RestClient并选择json作为RequestFormat选项将一个简单的对象发送到服务器?我将输出发送到测试服务器,当我用二进制编辑器检查输出时,只看到1d。 我希望5C 75 30 30 31 64(' \',' u'' 0',' 0',' 1',' d')如果您只是使用NewtonSoft.Json来序列化包含字符串" \ u001d"的同一个简单对象,那就是您所看到的。我的理解是,RestSharp会将您的对象序列化为.json(如果您选择相应的选项)并发送到服务器进行反序列化。

using Newtonsoft.Json;
using RestSharp;
using RestSharp.Serializers;
using System;
using System.Collections.Generic;
using System.IO;

/// <summary>
/// To Test this,
/// 1. I first serialize a string to JSON and then print out the JSON string as a character array to show that
/// '\' 'u' '0' '0' '1' 'd' all show up in the serialized json string.  I also put in original string \\001d (double \) for use later with RestSharp.
/// 2. I expect this json string to show up if I use RestSharp to send an object to a web service because RestSharp serializes everything to JSON
/// if it's so configured. 
///  I make use of http://posttestserver.com/ to 
/// act as "server", so I can just examine the data on that site. I copy the data on the server to a text file and open with binary editor.
/// Notice that there is just a '1d' present between 12 and 34.  
/// You can perhaps duplicate what you get with just serialization by starting with (double \). But why should you need to do that?
/// Notice I try both RestClient's serializer and try to use NewtonSoft Serializer w/ RestClient.
/// 3.  Finally I tried to send the serialized string with a client that had not been set up for JSON.  Unfortunately, on server, it just shows up 
/// as <String />.  Not sure if it's me setting up client incorrectly or what's going on.
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        Tank tank1 = new Tank();
        string string1 = "12\u001d34" + "  " + "56\\u001d78" ; // you don't need double \\ with just serialization, but perhaps you need them with RestClient?
        tank1.Description = string1;// new string(array1);          
        tank1.Id = 1;

        // Show that we can serialize and each character of \u001d shows up as 
        JsonSerializerSettings settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
        string conversion1 = JsonConvert.SerializeObject(tank1, Formatting.Indented, settings);
        Console.WriteLine("JSON serialized string (showing hidden characters");

        foreach(char dummyChar in conversion1.ToCharArray())
        {
            Console.Write(dummyChar + "  ");
            //Console.Write(conversion1.ToCharArray()[i] + "  ");
        }            Console.WriteLine();

        // Demonstrate that straight RestClient doesn't work.
        RestClient client1 = new RestClient("http://posttestserver.com/"); // a website that let's you push data at it.

        var request = new RestRequest(Method.POST); // method is Method.POST
        request.AddHeader("Content-Type", "application/json");
        request.AddHeader("X-ACME-API-VERSION", "1");
        request.Resource = "post.php?dir=David";  // Put in unique name that you can easily find at http://posttestserver.com/data/2016/
        request.RequestFormat = DataFormat.Json;

        request.AddBody(tank1);

        var response = client1.Execute(request); // after this line, go examine http://posttestserver.com/data/2016/ and find your post
        // copy text to text file and open with binary editor.  I claim you will see a 1d but not / (47) or u (117) or 0 (48) i.e. just 1d but not \u001d

        // now try RequestClient w/ json serializer.... 
        request.JsonSerializer = new JsonSerializerNewtonSoft();

        response = client1.Execute(request);

        // Finally, try just sending the json serialized stuff with a RestClient that has NOT been set to json stufff
        RestClient client3 = new RestClient("http://posttestserver.com/"); // a website that let's you push data at it.
        request.Resource = "post.php?dir=David";  // Put in unique name that you can find at http://posttestserver.com/data/2016/

        var request3 = new RestRequest(Method.PUT); // method is Method.PUT // not sure what to put here
        //request3.AddHeader("Content-Type", "application/json"); // not sure what to use here if anything
        request3.AddHeader("X-ACME-API-VERSION", "1");
        request3.Resource = "post.php?dir=David";  // Put in unique name that you can find at http://posttestserver.com/data/2016/
        request3.RequestFormat = DataFormat.Xml; // not sure what to use here

        request3.AddBody(conversion1);

        var response3 = client3.Execute(request3); // hard to evaluate.  Shows up at test server as <String /> 
    }
}

interface ITank
{
    int Id { get; set; }
    string Description { get; set; }
}
public class Tank : ITank
{
    public Tank() { }
    public string Description { get; set; }
    public int Id { get; set; }
}

public class Tanks
{
    public Tanks() { }
    public IEnumerable<Tank> Group { get; set; }
}

public class JsonSerializerNewtonSoft : ISerializer
{
    private readonly Newtonsoft.Json.JsonSerializer _serializer;

    /// <summary>
    /// Default serializer
    /// </summary>
    public JsonSerializerNewtonSoft()
    {
        ContentType = "application/json";

        _serializer = new Newtonsoft.Json.JsonSerializer
        {
            MissingMemberHandling = MissingMemberHandling.Ignore,
            NullValueHandling = NullValueHandling.Include,
            DefaultValueHandling = DefaultValueHandling.Include
        };
    }

    /// <summary>
    /// Default serializer with overload for allowing custom Json.NET settings
    /// </summary>
    public JsonSerializerNewtonSoft(Newtonsoft.Json.JsonSerializer serializer)
    {
        ContentType = "application/json";
        _serializer = serializer;
    }

    /// <summary>
    /// Serialize the object as JSON
    /// </summary>
    /// <param name="obj">Object to serialize</param>
    /// <returns>JSON as String</returns>
    public string Serialize(object obj)
    {
        using (var stringWriter = new StringWriter())
        {
            using (var jsonTextWriter = new JsonTextWriter(stringWriter))
            {
                jsonTextWriter.Formatting = Formatting.Indented;
                jsonTextWriter.QuoteChar = '"';

                _serializer.Serialize(jsonTextWriter, obj);
                var result = stringWriter.ToString();
                return result;
            }
        }
    }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string DateFormat { get; set; }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string RootElement { get; set; }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string Namespace { get; set; }

    /// <summary>
    /// Content type for serialized content
    /// </summary>
    public string ContentType { get; set; }
}

1 个答案:

答案 0 :(得分:2)

首先,感谢您发布可用于重现问题的Minimal, Complete, and Verifiable example;这使得帮助更容易。

好的,这里发生了一些导致你看到的结果的事情。让我们一次看一个。

首先,您要创建一个这样的字符串:

string string1 = "12\u001d34" + "  " + "56\\u001d78";

你使用的反斜杠数确实很重要,因为它在C#中具有与在JSON中相同的特殊含义。具体来说,C#中的符号\uxxxx表示&#34;将带有4位十六进制数字(UTF-16)字符代码xxxx的Unicode字符插入字符串&#34;。相反,符号\\表示&#34;在字符串&#34;中插入单个\字符。因此,在字符串的第一部分中,您将插入单个字符0x001d,即组分隔符控制字符。在字符串的第二部分中,您要插入六个字符:\u001d。您可以通过一个简单的测试程序看到自己的区别,该程序将字符转储为十六进制数字:

public class Program
{
    public static void Main()
    {
        DumpCharsAsHex("\u001d");   // inserts actual 0x001d character (Group Separator) into string
        DumpCharsAsHex("\\u001d");  // inserts chars '\', 'u', '0', '0', '1', 'd' into string
    }

    private static void DumpCharsAsHex(string s)
    {
        if (s != null)
        {
            for (int i = 0; i < s.Length; i++)
            {
                int c = s[i];
                Console.Write(c.ToString("X") + " ");
            }
        }
        Console.WriteLine();
    }
}

输出:

1D 
5C 75 30 30 31 64

小提琴:https://dotnetfiddle.net/8tjIiX

其次,对于嵌入在字符串中的控制字符,Json.Net和SimpleJson(RestSharp内部序列化器)之间肯定存在不同的行为。 Json.Net识别控制字符并将它们转换为适当的JSON转义序列。 (此转换由JavaScriptUtils.WriteEscapedJavaScriptString方法完成,后者又调用StringUtils.ToCharAsUnicode。另一方面,RestSharp不进行此类转换,只是通过JSON传递不可见的控制字符。 (你可以在SimpleJson.EscapeToJavascriptString中看到这一点。)
同样,一个简单的测试程序证明了差异:

public class Program
{
    public static void Main()
    {
        Foo foo = new Foo { Bar = "\u001d" };

        string json = Newtonsoft.Json.JsonConvert.SerializeObject(foo);
        Console.WriteLine(json);
        DumpCharsAsHex(json);

        string json2 = RestSharp.SimpleJson.SerializeObject(foo);
        Console.WriteLine(json2);
        DumpCharsAsHex(json2);
    }

    private static void DumpCharsAsHex(string s)
    {
        if (s != null)
        {
            for (int i = 0; i < s.Length; i++)
            {
                int c = s[i];
                Console.Write(c.ToString("X") + " ");
            }
        }
        Console.WriteLine();
    }
}

public class Foo
{
    public string Bar { get; set; } 
}

输出:

{"Bar":"\u001d"}
7B 22 42 61 72 22 3A 22 5C 75 30 30 31 64 22 7D 
{"Bar":""}
7B 22 42 61 72 22 3A 22 1D 22 7D

小提琴:https://dotnetfiddle.net/caxZfq

正如您所看到的,在Json.Net生成的第一个JSON中,原始字符串中的控制字符被转换为JSON字符转义符号,巧合地看起来就像原始C#代码一样。当JSON在另一端反序列化时,它将被转换回控制字符。

在RestSharp生成的第二个JSON中,控制字符实际存在(1D之间的22),即使它在JSON输出中不可见。我应该注意到,根据JSON spec(强调我的)第9节,这绝对是不正确的行为:

  

字符串是用引号(U + 0022)包装的Unicode代码点序列。所有人物都可能是   放在引号中,但必须转义的字符除外:引号(U + 0022),   反向固相线(U + 005C),控制字符U + 0000至U + 001F

由于保留了控制字符,因此在通过电线传输期间或在另一端的反序列化期间,它可能会以不合需要的方式被破坏或重新解释。

第三,在您的代码中,您似乎正在尝试使用Json.Net作为RestSharp的替代序列化程序,但它似乎并没有对您的结果产生影响。原因是因为你的陈述无序。

你这样做:

var request = new RestRequest(Method.POST);
...
request.RequestFormat = DataFormat.Json;
request.AddBody(tank1);
request.JsonSerializer = new JsonSerializerNewtonSoft();
response = client1.Execute(request);

请注意,在为请求设置JsonSerializer之前,您正在调用AddBody RestRequest.AddBody是调用序列化程序获取JSON并将结果添加到请求主体的方法;这是RestClient.Execute 完成的。因此,当您设置备用JSON序列化程序时,为时已晚 - 您已经使用内部序列化程序将JSON添加到请求主体,并且永远不会调用备用序列化程序。颠倒这两个语句的顺序,它应该按照你想要的方式工作。

var request = new RestRequest(Method.POST);
...
request.RequestFormat = DataFormat.Json;
request.JsonSerializer = new JsonSerializerNewtonSoft();
request.AddBody(tank1);
response = client1.Execute(request);

希望这是有道理的。