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"?>
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.
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:
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);
}
RestSharp.Deserializers.XmlRestSerializer
and call .WithXmlDeserializer()
with the class from the step 2 above..AddHandler("application/xml", () => xmlRestSerializer)
on your RestClient
instance.
xmlRestSerializer
is the object you created in step 3 above.application/xml
with something else, depending on what the REST API returns.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With