RestSharp方法引发System.Xml.XMlException“ =”是意外令牌。预期令牌为“;”

时间:2019-06-01 22:01:59

标签: c# restsharp

我查看了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,但不确定并想知道是否是这种情况,或者我的代码是否还有其他问题?

2 个答案:

答案 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,并在解析之前将其删除。步骤如下:

  1. 子类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);
}
  1. 创建上述类的实例。
  2. 创建RestSharp.Deserializers.XmlRestSerializer的实例,并使用上述步骤2中的类调用.WithXmlDeserializer()
  3. 在您的.AddHandler("application/xml", () => xmlRestSerializer)实例上调用RestClient
    • xmlRestSerializer是您在上面的第3步中创建的对象。
    • 根据REST API返回的内容,您可能需要用其他内容替换application/xml