Методы RestSharp Бросок System.Xml.XMlException "=" является неожиданным токеном. Ожидаемый токен: ';' - PullRequest
8 голосов
/ 02 июня 2019

Я просмотрел ответы на на этот вопрос и вижу, что недопустимые символы могут вызвать проблемы, которые вызывают эту ошибку. Мой вопрос немного отличается тем, что я использую RestSharp для вызова API следующим образом:

 private static T Execute<T>(IRestRequest request, string baseUrl) where T : class, new()
    {
        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 ProPayResponse MerchantSignUpForProPay()
    {
        var baseUrl = "https://xmltestapi.propay.com/ProPayAPI";
        var request = BuildMerchantTestData();
        var restRequest = 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;
    }
private static string GetCredentials()
    {
        var termId = "myterm"; // put affiliate term id here, if you have it
        var certString = "mycertString"; // put affiliate cert string here
        var encodedCredentials = Convert.ToBase64String(Encoding.Default.GetBytes(certString + ":" + termId));

        var credentials = $"Basic {encodedCredentials}";
        return credentials;
    }

Полная трассировка стека исключения выглядит следующим образом:

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, но я не уверен и хотел бы знать, так ли это, или у моего кода есть другие проблемы?

UPDATE: После дальнейшего изучения этой проблемы, я думаю, что возникает ошибка, потому что я не сериализую свои объекты модели в JSON перед отправкой RestRequest.

Нужно ли сериализовать все мои объекты перед выполнением запроса?

Обновление 2: Благодаря второму набору глаз я исправил URL. Теперь, когда я запускаю свое приложение, выдается следующая ошибка:

Error: Exception: System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1.
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String arg)
at System.Xml.XmlTextReaderImpl.ParseRootLevelWhitespace()
at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
at System.Xml.XmlTextReaderImpl.Read()
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), Message: Data at the root level is invalid. Line 1, position 1., Headers: System.Collections.Generic.List`1[RestSharp.Parameter], Content: ?<?xml version="1.0" encoding="utf-8"?>

Ответы [ 2 ]

4 голосов
/ 06 июня 2019

Я думаю, что возникает ошибка, потому что я не сериализую свои объекты модели в JSON перед отправкой RestRequest.

restRequest.AddJsonBody(request); будет сериализовать объект и добавить соответствующий заголовокна запрос.Трассировка стека выглядит так, как будто проблема заключается в том, что ответ возвращается в виде XML и что происходит, когда он пытается его десериализовать.

Когда я запускаю этот код, я отмечаю, что HTTP 404 добавляется враздел содержимого трассировки стека.

Я думаю, это означает, что у меня неверный baseURl, но я не уверен и хотел бы знать, так ли это, или у моего кода есть другие проблемы?

Беглый взгляд на их документы, похоже, что вы вызываете их (SOAP) XML API.Таким образом, вы вызываете неправильный базовый URL, если вы хотите взаимодействовать с ProPay REST Interface.

Для REST они показывают следующие

URI ресурса и методы HTTP

URI запроса составляется из базового URI и добавляется URI ресурса.Ресурсный URI может использоваться по-разному в зависимости от HTTP-запроса.Рассмотрим следующий пример:

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-адрес настраиваемым, а не жестко закодированным, чтобы онможет быть легко изменено при запуске в производство без перекомпиляции.

2 голосов
/ 12 июня 2019

После вашего обновления 2 похоже, что RestSharp вводит неожиданный символ в начале XML.

Это из сообщения об ошибке:

Content: ?<?xml version="1.0" encoding="utf-8"?>

Знак вопроса перед<?xml это проблема.Это недопустимый символ для XML, и он заставляет синтаксический анализатор XML выдавать ошибку.

Мое лучшее предположение здесь заключается в том, что содержимое XML в ответе имеет метку порядка байтов UTF-8 (BOM) наНачните.Технически спецификация не является допустимым символом, и ваш код / ​​структура логирования преобразует его в ? для отображения.

Вы можете проверить это, вызвав .ExecuteTaskAsync(request) вместо .ExecuteTaskAsync<T>(request) и посмотрев наданные возвращаются в response.RawBytes.Если возвращаются первые 3 байта 0xEF 0xBB 0xBF, то в вашем ответе есть спецификация.

Быстрое исправление

Это должно выполнить работу и требует минимального кодаИзменения.

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;
    }
};

Это обеспечит скорейшее удаление спецификации.Когда он преобразуется в строку для анализа XML, спецификация не будет присутствовать.

Исправление длины

Вы можете создать собственный десериализатор для XML, который обнаружит спецификациюв начале XML и удаляет его перед анализом.Шаги здесь:

  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);
}
Создайте экземпляр вышеупомянутого класса. Создайте экземпляр RestSharp.Deserializers.XmlRestSerializer и вызовите .WithXmlDeserializer() с классом из шага 2 выше. Вызов .AddHandler("application/xml", () => xmlRestSerializer) на вашемRestClient экземпляр.
  • xmlRestSerializer - это объект, который вы создали на шаге 3.
  • Возможно, вам придется заменить application/xml чем-то другим, в зависимости от того, что возвращает REST API.
...