Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RestSharp Methods Throw System.Xml.XMlException "=" is an unexpected token. The expected token is ';'

I reviewed the answers to this question and see that invalid characters can cause issues that throw this error. My question is a tad different in that I'm using RestSharp to make an API call as follows:

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

The full stack trace of the exception is as follows:

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:

When I run this code, I do note that an HTTP 404 is thrown in the content section of the stack trace.

I think this means that I have an incorrect baseURl but am not sure and would like to know if this is the case or if my code has other issues?

UPDATE: After researching this issue further, I think the error is being thrown because I'm not serializing my model objects into JSON before sending the RestRequest.

Do I need to serialize all of my objects before making the request?

Update 2: Thanks to a second set of eyes, I corrected the URL. Now, when I run my application, the following error is thrown:

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"?>
like image 843
SidC Avatar asked Jun 01 '19 22:06

SidC


2 Answers

I think the error is being thrown because I'm not serializing my model objects into JSON before sending the RestRequest.

restRequest.AddJsonBody(request); will serialize the object and add the appropriate header to the request. The stack trace looks like the issue is with the response being returned as XML and what happens when it tries to desrialize it.

When I run this code, I do note that an HTTP 404 is thrown in the content section of the stack trace.

I think this means that I have an incorrect baseURl but am not sure and would like to know if this is the case or if my code has other issues?

Taking a quick look at their docs it looks like you are calling their (SOAP) XML API. So you are calling the wrong base URL, if the intention is to interact with ProPay REST Interface.

For REST they show the following

Resource URI and HTTP Methods

The request URI is constructed from a Base URI and a Resource URI appended. A Resource URI may be used differently based on the HTTP verb of the request. Consider the following Example:

ProPay Integration environment Base URI: https://xmltestapi.propay.com
Resource: /propayAPI/signup
HTTP Method: PUT
Request Endpoint: PUT https://xmltestapi.propay.com/propayapi/signup

Which would mean you need to update your code

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

I would suggest making the base URL configurable instead of hard coded so that it can be easily changed when going into production without having to recompile.

like image 70
Nkosi Avatar answered Nov 13 '22 22:11

Nkosi


Following your Update 2, it looks like RestSharp is introducing an unexpected character at the start of the XML.

This is from the error message:

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

The question mark before <?xml is the problem. It's not a valid character for XML, and is causing the XML parser to throw an error.

My best guess here is that the XML content in the response has a UTF-8 byte order mark (BOM) at the start. The BOM is not technically a valid character, and your logging code/framework is converting it to a ? for display.

You could test this by calling .ExecuteTaskAsync(request) instead of .ExecuteTaskAsync<T>(request) and looking at the data coming back in response.RawBytes. If the first 3 bytes coming back are 0xEF 0xBB 0xBF then you have a BOM in your response.

Quick fix

This should do the job, and requires minimal code changes.

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

This will ensure that the BOM is removed early on. When it's converted to a string for XML parsing, the BOM will not be present.

Lengthier fix

You can create your own deserializer for XML, which detects the BOM at the start of the XML and removes it before parsing. The steps here are:

  1. Subclass RestSharp.Deserializers.XmlDeserializer. This will need a single method override:
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. Create an instance of the above class.
  2. Create an instance of RestSharp.Deserializers.XmlRestSerializer and call .WithXmlDeserializer() with the class from the step 2 above.
  3. Calling .AddHandler("application/xml", () => xmlRestSerializer) on your RestClient instance.
    • xmlRestSerializer is the object you created in step 3 above.
    • You may need to replace application/xml with something else, depending on what the REST API returns.
like image 34
NZgeek Avatar answered Nov 13 '22 22:11

NZgeek