RestSharp调用NetApp REST API POST请求报Invalid JSON input错误
在C#应用程序中使用RestSharp访问NetApp REST API时,GET请求可正常执行,但发送POST请求时始终返回错误:
"Invalid JSON input. Unexpected character in stream: r around 0:0."
环境约束:受项目其他依赖限制,当前固定使用RestSharp 105.2.3版本。由于应用体量较大,升级RestSharp需要修改多个模块并适配更高版本.NET框架,且当前版本不支持
AddStringBody方法。
相关代码
请求发送核心方法
负责构造、发送请求并校验返回结果的CreateQtree方法实现:
internal bool CreateQtree(Pool pool, BaseStorageConf storageConf, RestClientCustom server) { bool isOk = false; // Prepare request query parameters. var request = new RestRequest("storage/qtrees"); request.Method = Method.POST; request.RequestFormat = DataFormat.Json; request.AddParameter("return_records", "false"); request.AddParameter("return_timeout", 15); // Prepare body. PostCreateQTree CreateQTreePost = new PostCreateQTree(); CreateQTreePost.Name = pool.Name; CreateQTreePost.SecurityStyle = "unix"; CreateQTreePost.Svm = new Svm(); CreateQTreePost.Svm.Name = storageConf.FileServer; CreateQTreePost.Volume = new Volume(); CreateQTreePost.Volume.Name = storageConf.FileSystem; // Assure that null values are not serialized. JsonSerializerSettings setting = new JsonSerializerSettings(); setting.NullValueHandling = NullValueHandling.Ignore; // Using this serializer makes sure that the JsonProperty are honored. string body = JsonConvert.SerializeObject(CreateQTreePost, setting); request.AddParameter("application/json", body, ParameterType.RequestBody); // Execute request. var response = server.Execute(request); // Handle results. if (response.StatusCode == HttpStatusCode.OK) { // Creation of the qtree was successfull. TheLogger.Instance.Error(string.Format("Qtree could be created. Pool: {0}", pool.Name)); isOk = true; } else { // There was an error, dump. ResponseError ErrorResponse = JsonConvert.DeserializeObject<ResponseError>(response.Content); TheLogger.Instance.Error(string.Format("Qtree could not be created. Pool: {0}, code: {1}, message: {2}", pool.Name, ErrorResponse.Error.Code, ErrorResponse.Error.Message)); } return isOk; }
自定义调试客户端
RestClientCustom为继承自RestClient的自定义实现,用于生产环境打印调试日志:
using Logging; using RestSharp; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; namespace Devices.NetAppREST { internal class RestClientCustom:RestClient { private readonly bool mIsDebugMode; internal RestClientCustom(string baseUrl, bool isDebugMode): base(baseUrl) { mIsDebugMode = isDebugMode; } internal RestClientCustom(Uri baseUrl, bool isDebugMode) : base(baseUrl) { mIsDebugMode = isDebugMode; } public IRestResponse Execute(RestRequest request) { IRestResponse response; TheLogger.Instance.Debug(string.Format("REST URI:\r\n{0}", base.BuildUri(request))); TheLogger.Instance.Debug(string.Format("Parameters:\r\n{0}", ParameterToString(request))); if (mIsDebugMode) { response = new RestResponse(); response.StatusCode = HttpStatusCode.OK; return response; } else { response = base.Execute(request); } TheLogger.Instance.Debug(string.Format("StatusCode:\r\n{0}", response.StatusCode)); TheLogger.Instance.Debug(string.Format("Content:\r\n{0}", response.Content)); TheLogger.Instance.Debug(string.Format("Headers:\r\n{0}", response.Headers)); TheLogger.Instance.Debug(string.Format("ResponseURI:\r\n{0}", response.ResponseUri)); TheLogger.Instance.Debug(string.Format("ErrorMessage:\r\n{0}", response.ErrorMessage)); return response; } internal String ParameterToString(RestRequest request) { var sb = new StringBuilder(); foreach (var param in request.Parameters) { sb.AppendFormat("Name: {0}, Value: {1}, Type: {2}\r\n", param.Name, param.Value, param.Type.ToString()); } return sb.ToString(); } } }
请求体实体类
用于生成POST请求体JSON的实体类定义,配合Newtonsoft.Json保证字段名符合接口要求、过滤null值:
using Newtonsoft.Json; namespace Devices.NetAppREST { public class PostCreateQTree { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("security_style")] public string SecurityStyle { get; set; } [JsonProperty("svm")] public Svm Svm { get; set; } [JsonProperty("volume")] public Volume Volume { get; set; } } }
序列化后生成的示例JSON如下,经校验格式完全合法:
{"name":"G-IPH-TestPoolCreate","security_style":"unix","svm":{"name":"sy-fs-301"},"volume":{"name":"g"}}
该JSON通过curl、PowerShell或接口直接调用均可正常执行,但通过上述RestSharp实现发送始终报错。先后尝试更换RestSharp默认序列化器(会生成大写字段名不符合接口要求)、直接硬编码JSON字符串作为参数均无法解决问题,调试时查看请求参数、URI均无明显异常。
问题出在查询参数的添加逻辑:RestSharp 105.2.3版本中,调用AddParameter方法时如果不显式指定参数类型,框架不会自动将return_records、return_timeout识别为URL查询参数,反而会将这两个参数拼接到请求体内容最前方,直接破坏原本合法的JSON结构。
报错信息中提到的意外字符r,正是被错误拼接的第一个参数return_records的首字符——服务端解析请求体时第一个字符不是JSON合法的起始符号({/[),因此抛出JSON格式非法错误。
给两个URL查询参数显式指定参数类型为ParameterType.QueryString即可。
- 原错误写法:
request.AddParameter("return_records", "false"); request.AddParameter("return_timeout", 15);
- 修正后写法:
request.AddParameter("return_records", "false", ParameterType.QueryString); request.AddParameter("return_timeout", 15, ParameterType.QueryString);
修改后POST请求即可正常调用。
内容的提问来源于stack exchange,提问作者Philipp Hungerbühler

