我查看了this question的答案,发现无效字符会导致引发此错误的问题。我的问题有点不同,因为我使用RestSharp进行如下的API调用:
private static T Execute<T>(IRestRequest request, string baseUrl) where T : class, new()
{
//baseUrl = "https://xmltest.propay.com/api/propayapi/PropayMerchantService.svc/";
var client = new RestClient(baseUrl);
var response = client.Execute<T>(request);
if (response.ErrorException != null)
{
Console.WriteLine(
"Error: Exception: {0}, Headers: {1}, Content: {2}, Status Code: {3}",
response.ErrorException,
response.Headers,
response.Content,
response.StatusCode);
}
return response.Data;
}
public static async Task<ProPayResponse> MerchantSignUpForProPay()
{
var baseUrl = "https://xmltest.propay.com/api/propayapi/PropayMerchantService.svc/";
var request = await BuildMerchantTestData();
var restRequest = await CreateRestRequest("/Signup", Method.PUT);
restRequest.AddJsonBody(request);
return Execute<ProPayResponse>(restRequest, baseUrl);
}
private static async Task<RestRequest> CreateRestRequest(string resource, Method method)
{
var credentials = GetCredentials();
var restRequest = new RestRequest { Resource = resource, Method = method, RequestFormat = DataFormat.Json, };
restRequest.AddHeader("accept", "application/json");
restRequest.AddHeader("Authorization", credentials);
return restRequest;
}
该异常的完整堆栈跟踪如下:
Error: Exception: System.Xml.XmlException: '=' is an unexpected token. The expected token is ';'. Line 26, position 43.
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(String expectedToken1, String expectedToken2)
at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos)
at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& outOrChars)
at System.Xml.XmlTextReaderImpl.FinishPartialValue()
at System.Xml.XmlTextReaderImpl.get_Value()
at System.Xml.Linq.XContainer.ContentReader.ReadContentFrom(XContainer rootContainer, XmlReader r)
at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r)
at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r, LoadOptions o)
at System.Xml.Linq.XDocument.Load(XmlReader reader, LoadOptions options)
at System.Xml.Linq.XDocument.Parse(String text, LoadOptions options)
at RestSharp.Deserializers.XmlDeserializer.Deserialize[T](IRestResponse response)
at RestSharp.RestClient.Deserialize[T](IRestRequest request, IRestResponse raw), Headers: System.Collections.Generic.List`1[RestSharp.Parameter], Content:
运行此代码时,我确实注意到在堆栈跟踪的内容部分中引发了HTTP 404。
我认为这意味着我有一个错误的baseURl
,但不确定并想知道是否是这种情况,或者我的代码是否还有其他问题?
答案 0 :(得分:4)
我认为引发了错误,因为我没有在发送RestRequest之前将模型对象序列化为JSON。
restRequest.AddJsonBody(request);
将序列化对象并将适当的标头添加到请求中。堆栈跟踪看起来像是问题所在,响应以XML格式返回,并且尝试对它进行反序列化时会发生什么。
运行此代码时,我确实注意到在堆栈跟踪的内容部分中抛出了HTTP 404。
我认为这意味着我的baseURl不正确,但是不确定,是否想知道是这种情况还是我的代码还有其他问题?
快速浏览他们的文档,就好像您在调用他们的(SOAP)XML API。因此,如果要与ProPay REST接口进行交互,则您调用的基本URL错误。
对于REST,它们显示以下内容
资源URI和HTTP方法
请求URI由基本URI和附加的资源URI构成。根据请求的HTTP动词,可以不同地使用资源URI。考虑以下示例:
ProPay Integration environment Base URI: https://xmltestapi.propay.com Resource: /propayAPI/signup HTTP Method: PUT Request Endpoint: PUT https://xmltestapi.propay.com/propayapi/signup
这意味着您需要更新代码
public static async Task<ProPayResponse> MerchantSignUpForProPay() {
var baseUrl = "https://xmltestapi.propay.com/propayapi";
var content = await BuildMerchantTestData();
var request = CreateRestRequest("Signup", Method.PUT);
request.AddJsonBody(content);
return await Execute<ProPayResponse>(request, baseUrl);
}
private static async Task<T> Execute<T>(IRestRequest request, string baseUrl)
where T : class, new() {
var client = new RestClient(baseUrl);
var response = await client.ExecuteTaskAsync<T>(request);
if (response.ErrorException != null) {
Console.WriteLine(
"Error: Exception: {0}, Headers: {1}, Content: {2}, Status Code: {3}",
response.ErrorException,
response.Headers,
response.Content,
response.StatusCode);
}
return response.Data;
}
private static RestRequest CreateRestRequest(string resource, Method method) {
var credentials = GetCredentials();
var restRequest = new RestRequest(resource, method, DataFormat.Json);
restRequest.AddHeader("Accept", "application/json");
restRequest.AddHeader("Authorization", credentials);
return restRequest;
}
我建议您将基本URL设置为可配置的,而不是硬编码,以便在投入生产时可以很容易地对其进行更改,而无需重新编译。
答案 1 :(得分:2)
在更新2之后,似乎RestSharp在XML的开头引入了一个意外的字符。
这是来自错误消息:
Content: ?<?xml version="1.0" encoding="utf-8"?>
<?xml
前面的问号是问题。这不是XML的有效字符,并导致XML解析器抛出错误。
我的最佳猜测是,响应中的XML内容在开始时具有UTF-8字节顺序标记(BOM)。从技术上讲,该BOM表不是有效字符,并且您的日志记录代码/框架将其转换为?
以便显示。
您可以通过调用.ExecuteTaskAsync(request)
而不是.ExecuteTaskAsync<T>(request)
并查看response.RawBytes
中返回的数据来进行测试。如果返回的前3个字节为0xEF 0xBB 0xBF
,则您的响应中将包含BOM。
快速修复
这应该可以完成工作,并且需要最少的代码更改。
restRequest.OnBeforeDeserialization = resp => {
if (resp.RawBytes.Length >= 3 && resp.RawBytes[0] == 0xEF && resp.RawBytes[1] == 0xBB && resp.RawBytes[2] == 0xBF)
{
// Copy the data but with the UTF-8 BOM removed.
var newData = new byte[resp.RawBytes.Length - 3];
Buffer.BlockCopy(resp.RawBytes, 3, newData, 0, newData.Length);
resp.RawBytes = newData;
// Force re-conversion to string on next access
resp.Content = null;
}
};
这将确保BOM尽早被删除。当将其转换为用于XML解析的字符串时,BOM将不存在。
更强的修复力
您可以为XML创建自己的反序列化器,该反序列化器将在XML的开头检测BOM,并在解析之前将其删除。步骤如下:
RestSharp.Deserializers.XmlDeserializer
。这将需要一个方法覆盖:public override T Deserialize<T>(IRestResponse response)
{
if (string.IsNullOrEmpty(response.Content))
return default(T);
if (response.Content[0] == '\uFEFF')
response.Content = response.Content.Substring(1);
return base.Deserialize<T>(response);
}
RestSharp.Deserializers.XmlRestSerializer
的实例,并使用上述步骤2中的类调用.WithXmlDeserializer()
。.AddHandler("application/xml", () => xmlRestSerializer)
实例上调用RestClient
。
xmlRestSerializer
是您在上面的第3步中创建的对象。application/xml
。