RestSharp - XmlSerializer not using the DateFormat property - c#

I encountered a problem with the RestSharp library. Defaultly, it serializes DateTime objects using the format dd/MM/yyyy HH:mm:ss. That doesn't work well with my WCF service that only seems to accept yyyy-MM-ddTHH:mm:ss, so I tried to alter the serialization of a request with request.DateFormat = "yyyy-MM-ddTHH:mm:ss.
This property , even though set correctly, seems to be having zero impact on the serialization. At least when using the default RestSharp.Serializers.XmlSerializer. If I tried using the DotNetXmlSerializer, the DateFormat was working, but then the serializer didn't include my XMLNS link and added version & encoding line to the xml output, one or both of which wasn't compatible with the WCF service either.
Does anybody have any suggestions what am I doing wrong with the XmlSerializer ?
Here is the concerned codeblock:
var req = new RestRequest(endpoint, Method.POST);
req.RequestFormat = DataFormat.Xml;
//req.XmlSerializer = new DotNetXmlSerializer();
req.XmlSerializer = new XmlSerializer();
req.DateFormat = DATE_FORMAT;
req.AddBody(model, XMLNS);
Where private const string DATE_FORMAT = "yyyy-MM-ddTHH:mm:ss" and XMLNS is the URL used in the WCF requests (taken from the endpoint /help documentation).

Looks like RestRequest.DateFormat is only used when deserializing:
/// <summary>
/// Used by the default deserializers to explicitly set which date format string to use when parsing dates.
/// </summary>
public string DateFormat { get; set; }
For serializing you need to set it explicitly on the serializer:
req.XmlSerializer = new XmlSerializer { DateFormat = DATE_FORMAT };
Note that, for DotNetXmlSerializer, the underlying System.Xml.Serialization.XmlSerializer does not support custom DateTime formats, according to this answer.

Related

Apply IsoDateTimeConverter to Incoming Request

I created a Web API in .Net, I added the global settings below:
var jsonSettings = new JsonSerializerSettings();
jsonSettings .Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffZ" });
Im always getting the date format yyyy-MM-ddTHH:mm:ss.fffZ but now I wish to change my setting to only convert on incoming not outgoing. e.g incoming 2022-08-22T13:42:27.407Z and outgoing 2022-08-22 13:42:27, I moved the setting to an ActionFilterAttribute OnActionExecuting, but doesnt work right. the date is already converted (ISO String setting 2 hours diff) when it reaches the OnActionExecuting function.
Is possible to change format for outgoing date format?
You could inherit from IsoDateTimeConverter so it overrides CanRead to return false. Once done, the new converter will apply only for writing, and default serialization will be used for reading.
First, create the following converter:
public class WriteOnlyIsoDateTimeConverter : IsoDateTimeConverter
{
public override bool CanRead => false;
}
And now the following unit test will pass:
var json = #"""2022-08-22T13:42:27.407Z""";
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new WriteOnlyIsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
var dateTime = JsonConvert.DeserializeObject<DateTime>(json, jsonSettings);
var json2 = JsonConvert.SerializeObject(dateTime, jsonSettings);
Assert.AreEqual(#"""2022-08-22 13:42:27""", json2);
Notes:
2022-08-22T13:42:27.407Z is already in the default serialization format used by Json.NET for DateTime objects, but if you need to change the default you can set JsonSerializerSettings.DateFormatString or JsonSerializerSettings.DateTimeZoneHandling. The converter will be used for writing, and the (modified) defaults will be used for reading.
If you want a converter to convert apply only when reading, override CanWrite instead.
If you do want your custom DateTime converter to handle reading as well as writing, you may need to set JsonSerializerSettings.DateParseHandling = DateParseHandling.None to prevent JsonTextReader from automatically recognizing and parsing it before the ReadJson() method of your converter is called. For details see Json.NET Disable the deserialization on DateTime.
Demo fiddle here.

Unexpected character encountered while parsing value

Currently, I have some issues. I'm using C# with Json.NET. The issue is that I always get:
{"Unexpected character encountered while parsing value: e. Path '', line 0, position 0."}
So the way I'm using Json.NET is the following. I have a Class which should be saved. The class looks like this:
public class stats
{
public string time { get; set; }
public string value { get; set; }
}
public class ViewerStatsFormat
{
public List<stats> viewerstats { get; set; }
public String version { get; set; }
public ViewerStatsFormat(bool chk)
{
this.viewerstats = new List<stats>();
}
}
One object of this class will be filled and saved with:
File.WriteAllText(tmpfile, JsonConvert.SerializeObject(current), Encoding.UTF8);
The saving part works fine and the file exists and is filled. After that the file will be read back into the class with:
try
{
ViewerStatsFormat current = JsonConvert.DeserializeObject<ViewerStatsFormat>(tmpfile);
//otherstuff
}
catch(Exception ex)
{
//error loging stuff
}
Now on the current= line comes the exception:
{"Unexpected character encountered while parsing value: e. Path '', line 0, position 0."}
I don't know why this comes. The JSON file is the following -> Click me I am the JSON link
Does anyone have any ideas?
Possibly you are not passing JSON to DeserializeObject.
It looks like from File.WriteAllText(tmpfile,... that type of tmpfile is string that contain path to a file. JsonConvert.DeserializeObject takes JSON value, not file path - so it fails trying to convert something like #"c:\temp\fooo" - which is clearly not JSON.
I solved the problem with these online tools:
To check if the Json structure is OKAY: http://jsonlint.com/
To generate my Object class from my Json structure: https://www.jsonutils.com/
The simple code:
RootObject rootObj= JsonConvert.DeserializeObject<RootObject>(File.ReadAllText(pathFile));
In my case, the file containing JSON string had BOM. Once I removed BOM the problem was solved.
I experienced the same error in my Xamarin.Android solution.
I verified that my JSON was correct, and noticed that the error only appeared when I ran the app as a Release build.
It turned out that the Linker was removing a library from Newtonsoft.JSON, causing the JSON to be parsed incorrectly.
I fixed the error by adding Newtonsoft.Json to the Ignore assemblies setting in the Android Build Configuration (screen shot below)
JSON Parsing Code
static readonly JsonSerializer _serializer = new JsonSerializer();
static readonly HttpClient _client = new HttpClient();
static async Task<T> GetDataObjectFromAPI<T>(string apiUrl)
{
using (var stream = await _client.GetStreamAsync(apiUrl).ConfigureAwait(false))
using (var reader = new StreamReader(stream))
using (var json = new JsonTextReader(reader))
{
if (json == null)
return default(T);
return _serializer.Deserialize<T>(json);
}
}
Visual Studio Mac Screenshot
Visual Studio Screenshot
I have also encountered this error for a Web API (.Net Core 3.0) action that was binding to a string instead to an object or a JObject. The JSON was correct, but the binder tried to get a string from the JSON structure and failed.
So, instead of:
[HttpPost("[action]")]
public object Search([FromBody] string data)
I had to use the more specific:
[HttpPost("[action]")]
public object Search([FromBody] JObject data)
This issue is related to Byte Order Mark in the JSON file. JSON file is not encoded as UTF8 encoding data when saved. Using File.ReadAllText(pathFile) fix this issue.
When we are operating on Byte data and converting that to string and then passing to JsonConvert.DeserializeObject, we can use UTF32 encoding to get the string.
byte[] docBytes = File.ReadAllBytes(filePath);
string jsonString = Encoding.UTF32.GetString(docBytes);
I had the same problem with webapi in ASP.NET core, in my case it was because my application needs authentication, then it assigns the annotation [AllowAnonymous] and it worked.
[AllowAnonymous]
public async Task <IList <IServic >> GetServices () {
        
}
I ran into this issue and it ended up being because of BOM characters in my input string.
Here's what I ended up doing:
String.Trim(new char[] { '\uFEFF', '\u200B' });
This resolved the issue for me.
In my case, I was getting an error on JsonConvert.PopulateObject().
My request was returning JSON that was wrapped in an extra pair of '[ ]' brackets, making my result an array of one object rather than just an object. Here's what I did to get inside these brackets (only for that type of model):
T jsonResponse = new T();
var settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.DateTimeOffset,
NullValueHandling = NullValueHandling.Ignore,
};
var jRslt = response.Content.ReadAsStringAsync().Result;
if (jsonResponse.GetType() == typeof(myProject.Models.MyModel))
{
var dobj = JsonConvert.DeserializeObject<MyModel[]>(jRslt);
var y = dobj.First();
var szObj = JsonConvert.SerializeObject(y);
JsonConvert.PopulateObject(szObj, jsonResponse, settings);
}
else
{
JsonConvert.PopulateObject(jRslt, jsonResponse);
}
If you are using downloading data using url...may need to use
var result = client.DownloadData(url);
In my scenario I had a slightly different message, where the line and position were not zero.
E. Path 'job[0].name', line 1, position 12.
This was the top Google answer for the message I quoted.
This came about because I had called a program from the Windows command line, passing JSON as a parameter.
When I reviewed the args in my program, all the double quotes got stripped.
You have to reconstitute them.
I posted a solution here. Though it could probably be enhanced with a Regex.
I had a similar error and thought I'd answer in case anyone was having something similar. I was looping over a directory of json files and deserializing them but was getting this same error.
The problem was that it was trying to grab hidden files as well. Make sure the file you're passing in is a .json file. I'm guessing it'll handle text as well. Hope this helps.
I had simular problem. In my case the problem was in DateTime format. It was just numbers and it is also know as EpochFormat or UnixTimestamp.
A part from my JSON:
"direction": "outbound",
"date_archive": 1554691800224,
"date_doc": 1524700800000,
"date_sent": 1524704189000,
"date_received": 1524704189000,
"date_store_till": 1712544600224,
So I've used an attribute like this:
[JsonProperty("date_received")]
[JsonConverter(typeof(MicrosecondEpochConverter))]
public DateTime? DateReceived { get; set; }
You can find MicrosecondEpochConverter code here: https://stackoverflow.com/a/19972214/4324624
I faced similar error message in Xamarin forms when sending request to webApi to get a Token,
Make sure all keys (key : value) (ex.'username', 'password', 'grant_type') in the Json file are exactly what the webApi expecting, otherwise it fires this exception.
Unhandled Exception: Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0
Please check the model you shared between client and server is same. sometimes you get this error when you not updated the Api version and it returns a updated model, but you still have an old one. Sometimes you get what you serialize/deserialize is not a valid JSON.
In my case, it was the lack of a default parameterless constructor !!!
In my case, I was calling the async service method without using await, so before Task is completed I was trying to return the result!
Suppose this is your json
{
"date":"11/05/2016",
"venue": "{\"ID\":12,\"CITY\":Delhi}"
}
if you again want deserialize venue, modify json as below
{
"date":"11/05/2016",
"venue": "{\"ID\":\"12\",\"CITY\":\"Delhi\"}"
}
then try to deserialize to respective class by taking the value of venue
This error occurs when we parse json content to model object. Json content type is string.
For example:
https://dotnetfiddle.net/uFClKj
Some times, an api that we call may return an error. If we do not check the response status, but proceed to parse the response to model, this issue will occur.
When I encountered a similar problem, I fixed it by substituting &mode=xml for &mode=json in the request.

ServiceStack UK Date Binding on HTTP POST

I am using a mono self hosted servicestack application with the ServiceStack.Razor rendering. In the application the user enters into a form a UK date (dd/mm/yyyy) but this is converted to a US date (mm/dd/yyyy) on a HTTP POST.
In a normal MVC application I would do this using model binding as shown here ASP.NET MVC3: Force controller to use date format dd/mm/yyyy
How do you do this in ServiceStack as I could not find anything about it.
You can use custom serializers/deserializers to globally control the serialization and deserialization of DateTime values:
In your AppHost:
using ServiceStack.Text;
JsConfig<DateTime>.SerializeFn = SerializeAsUKDate;
// Also, if you need to support nullable DateTimes:
JsConfig<DateTime?>.SerializeFn = SerializeAsNullableUKDate;
public static string SerializeAsUKDate(DateTime value)
{
// or whatever you prefer to specify the format/culture
return value.ToString("dd/MM/yyyy");
}
public static string SerializeAsNullableUKDate(DateTime? value)
{
return value.HasValue ? SerializeAsUKDate(value.Value) : null;
}
You may or may not need to specify DeSerializeFn to ensure that dates are parsed correctly. The ServiceStack.Text date deserializer is pretty robust.
JsConfig<DateTime>.DeSerializeFn = DeSerializeAsUKDate;
public static DateTime DeSerializeAsUKDate(string value)
{
// date parsing logic here
// ServiceStack.Text.Common.DateTimeSerializer has some helper methods you may want to leverage
}

Read JSON date from file

I write my logs to a text file as JSON. In the file the call obejct LogTime value is
"1378289277591".
*{"LogTime":"Date(1378290565240)"}*
Consider the code below:
Public Class Sync{
public async Task<CallModel> ConvertCallFileToCallObejct(string path)
{
try
{
using (var sr = new StreamReader(path))
{
string callText = await sr.ReadToEndAsync();
var call = new JavaScriptSerializer().Deserialize<CallModel>(callText);
return call;
}
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
I convert the Call File to Call Object:
var sync = new Sync();
CallModel call = sync.ConvertCallFileToCallObejct(e.FullPath).GetAwaiter().GetResult();
The problem is that Call.LogTime is 9/4/2013 10:29:25 AM but Using Chrome Console and new Date(1378290565240) the result is 9/4/2013 14:59:25 PM
What is the problem?
try below code
// JSON received from server is in string format
var jsonString = '{"date":1251877601000}';
//use JSON2 or some JS library to parse the string
var jsonObject = JSON.parse( jsonString );
//now you have your date!
alert( new Date(jsonObject.date) );
I'm not sure what your Time zone is but I would expect that its UTC datetime.
According to your profile, you live in Iran, where the timezone is UTC+3:30. However, in April, Iran uses daylight saving time so the real timezone is UTC+4:30.
This means that UTC time of 9/4/2013 10:29:25 AM is 9/4/2013 14:59:25 PM local time in Iran.
According to ECMA specification, the time given in your JSON string is treated as UTC time, and it is deserialized as such. You can check the return value of Call.LogTime expression which returns DateTimeKind.Utc. Thus, what you see in your C# code is UTC time.
Now, Chrome also sees this time as UTC time, however it seems to display it as local time, according to your timezone. I am not 100% sure, but I think that Chrome uses your list of preferred languages when choosing how to display date, so try to play with it - I have no idea what exactly it does, but I remember a similar problem when changing the language order affected how time was interpreted. OF course, it depends on what exactly you try to achieve - IMO, both values are correct, as it is the same time.

How to deserialize WCF message using OperationContract

I succeeded in building a WCF client generated by svcutil.exe from the WSDL. Using the generated client proxy class I can call the web service of an external service supplier. I also succeeded in coding a message inspector, as I need to log both raw XML request and response as full SOAP message to the database.
For an emergency scenario I also need to be able to "import" a raw XML response. I found many hints on using XMLSerializer or deserializing WCF messages based on the message contract.
But how can I deserialize a raw XML response based on an operation contract? For a first test I use one of the logged raw responses, save it to a file and now try to deserialize it to the response type as generated in the client proxy. Somehow I must succeed in calling DeserializeReply() from class ClientOperation. But how to get there?
I happily accept any help as I'm quite new to WCF...
TIA,
Stefan
This is what I tried after Marc's answer:
public static RatingResult DeserializeResponseFromFile(string path)
{
var xmlReader = XmlReader.Create(path);
var message = Message.CreateMessage(xmlReader, int.MaxValue, MessageVersion.Soap11);
var readerAtBodyContents = message.GetReaderAtBodyContents();
var dcs = new DataContractSerializer(typeof(RatingResult), "RatingResponse", "http://rating.webservice.xxx.de");
// Error in line 6 position 7. 'EndElement' 'RatingResponse' from namespace
// 'http://rating.webservice.xxx.de' is not expected.
// Expecting element 'commonDataField'.
var wsResult = (RatingResult)dcs.ReadObject(readerAtBodyContents);
return wsResult;
}
This is part of the logged XML response file, that I'm trying to deserialize to type RatingResponse:
<soapenv:Envelope xmlns:soapenv="..." xmlns:soapenc="..." xmlns:xsd="..." xmlns:xsi="...">
<soapenv:Header soapenv:encodingStyle="..." />
<soapenv:Body soapenv:encodingStyle="...">
<p933:RatingResponse xmlns:p933="http://rating.webservice.xxx.de">
<RatingReturn href="#id0" />
</p933:RatingResponse>
<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="..." xsi:type="p878:RatingResult" xmlns:p878="http://output.rating.webservice.xxx.de">
<commonData href="#id1" />
<acctData href="#id2" />
<resultData href="#id3" />
</multiRef>
<multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="..." xsi:type="p719:RatingCommonData" xmlns:p719="http://input.rating.webservice.xxx.de">
<requestdate xsi:type="xsd:dateTime">2010-12-24T09:45:09.531Z</requestdate>
...
I guess that the data contract serializer has problems deserializing the href's. Please note that the message I try to deserialize "by hand" was captured using my injected message inspector. In a "normal" call of the web service this message get deserialized without problems.
I don't really understand what you're trying to ask and to do.... based on an operation contract ?? The operation contract is just an attribute you put on an operation / method call to mark it as a service method .... the operation contract doesn't do anything even remotely involved with serialization or deserialization..... do you mean how to deserialize an XML message using the DataContractSerializer which is the WCF default serializer??
Assuming you do really mean HOWTO: deserialize a WCF message using the DataContractSerializer, then try this: if you have the response XML from a service call that used the default WCF DataContractSerializer, you should be able to deserialize it like this (assuming you have your XML serialized response in a xmlResponse variable):
using(MemoryStream memStm = new MemoryStream())
using(StreamWriter stw = new StreamWriter(memStm))
{
// write your response to the memory stream
stw.Write(xmlResponse);
stw.Flush();
// "reset" memory stream
memStm.Seek(0, SeekOrigin.Begin);
// setup DataContractSerializer
DataContractSerializer dcs = new DataContractSerializer(typeof(YourDataType));
// deserialize result XML into an instance of "YourDataType"
var result = dcs.ReadObject(memStm);
}
For anyone in the future doing this. I had to manually read a WCF message out of the MSMSQ, and get the request object out of the MSMQ/WCF message envelope. Here's how:
Root code:
var q = new MessageQueue(#".\Private$\VishalQ;poison");
var allMessages = q.GetAllMessages().ToList();
var wcfRequests = allMessages.Select(ConvertToWcfRequest<ObjectChangedRequest>);
My contract:
[ServiceContract]
public interface IWish
{
[OperationContract(IsOneWay = true)]
void ObjectChanged(ObjectChangedRequest request);
}
My Data Contract:
[DataContract(Namespace = "http://x.namespaces.x-x.com/")]
public class ObjectChangedRequest
{
[DataMember]
public OperationType OperationType { get; set; }
}
My message deserialization code:
/// <summary>
/// Converts a WCF MSMQ message to a WCF request object.
/// </summary>
public static T ConvertToWcfRequest<T>(Message msmqMessage)
{
var buffer = new byte[msmqMessage.BodyStream.Length];
msmqMessage.BodyStream.Read(buffer, 0, (int)msmqMessage.BodyStream.Length);
var envelopeStart = FindEnvelopeStart(buffer);
using var msmqStream = new MemoryStream(buffer, envelopeStart, buffer.Length - envelopeStart);
var encodingElement = new BinaryMessageEncodingBindingElement();
var wcfMessage = encodingElement.CreateMessageEncoderFactory().Encoder.ReadMessage(msmqStream, int.MaxValue);
var document = new XmlDocument();
document.Load(wcfMessage.GetReaderAtBodyContents());
var realRoot = document.FirstChild.FirstChild;
using var wcfStream = new MemoryStream();
using var xmlWriter = XmlWriter.Create(wcfStream);
realRoot.WriteTo(xmlWriter);
xmlWriter.Flush();
wcfStream.Seek(0, SeekOrigin.Begin);
var wcfSerializer = new DataContractSerializer(typeof(T), realRoot.Name, "http://tempuri.org/"); //No idea why this has to be temp uri and not our namespace...
return (T)wcfSerializer.ReadObject(wcfStream);
}
/// <summary>
/// Locates the start of a WCF message within a MSMQ message.
/// </summary>
private static int FindEnvelopeStart(byte[] stream)
{
var position = 0;
var previousByte = stream[position];
for (position = 0; position < stream.Length; position++)
{
var currentByte = stream[position];
//Some magic numbers that define the start of the WCF message envelope
if (currentByte == 0x02 && previousByte == 0x56)
break;
previousByte = currentByte;
}
return position - 1;
}

Categories

Resources