Protobuf.net and serialization of DateTimeOffset - c#

I've been using Protobuf-net as the serializer for a thick client application that's using Service Stack to communicate over HTTP. Our first customer with a lot of volume has started seeing errors when deserializing. We are sending DateTimeOffset types in some of our models, and so we created a surrogate that serializes the value as a string. From our logs, I can see when the error occurs this is the date value it's attempting to deserialize has an extra six characters at the end where the timezone offset is repeated:
8/9/2016 12:02:37 AM-7:00 -7:00
Here's the code for our surrogate.
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public string DateTimeString { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate { DateTimeString = value.ToString() };
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
try
{
return DateTimeOffset.Parse(value.DateTimeString);
}
catch (Exception ex)
{
throw new Exception("Unable to parse date time value: " + value.DateTimeString, ex);
}
}
}
Once this date error has occurred, it won't correctly serialize/deserialize until the PC has rebooted. We have not been able to reproduce this error in a way that would allow us to debug and look at the rest of the message. Is this a situation that someone is familiar with? We were using version 2.0.0.640, and because of this issue I updated to 2.0.0.668 but the issue remains.

It looks as though the CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern is somehow getting messed up on the client's machine. I can reproduce the problem by adding the "K" format to the LongTimePattern:
var dateTime = DateTimeOffset.Parse(#"8/9/2016 12:02:37 AM-7:00");
var myCI = new CultureInfo("en-US");
myCI.DateTimeFormat.LongTimePattern = myCI.DateTimeFormat.LongTimePattern + " K";
Console.WriteLine(dateTime.ToString(myCI)); // Prints 8/9/2016 12:02:37 AM -07:00 -07:00
The string written is 8/9/2016 12:02:37 AM -07:00 -07:00 which is exactly what you are seeing.
It may be that there is a bug in your application which is setting the LongTimePattern somewhere. I can also reproduce the problem by doing:
Thread.CurrentThread.CurrentCulture = myCI;
Console.WriteLine(dateTime.ToString()); // Prints 8/9/2016 12:02:37 AM -07:00 -07:00
Or it may be that the client is somehow modifying the "Long time:" string in "Region and Language" -> "Additional settings..." dialog, which looks like (Windows 7):
If the client is doing this somehow, and the machine is on a domain, the format may get reset back on reboot which is exactly what you are seeing.
The client may be doing this manually (although, from experimentation, trying to append K manually on Windows 7 in the UI generates an error popup and then fails), or there may be some buggy 3rd party application doing it unbeknownst to you or them via a call to SetLocaleInfo.
You could log the value of LongTimePattern to try to trace the problem, but regardless you should modify your DateTimeOffsetSurrogate so that it serializes the DateTimeOffset in a culture-invariant format, preferably as specified by How to: Round-trip Date and Time Values: To round-trip a DateTimeOffset value:
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public string DateTimeString { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate { DateTimeString = value.ToString("o") };
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
try
{
return DateTimeOffset.Parse(value.DateTimeString, null, DateTimeStyles.RoundtripKind);
}
catch (Exception ex)
{
throw new Exception("Unable to parse date time value: " + value.DateTimeString, ex);
}
}
}
Not only should this fix the bug you are seeing, it will also ensure that protocol buffers generated by your app in one region (say, the UK) can be parsed elsewhere (say, the US) with different cultural formatting for dates and times.

Related

Passing date to ASMX web service

I use asmx web service writes in c# over android device.
I make connection and when in some web method I need integer or string like input param,all work great, but problem is when web method need date, I try to send date in many format but always I have problem to get answer.
I need to send date object, or like string? It is possible that web service view date like something else?
This is method for "communication" with web service:
public void connectSOAP()
{
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
String dateStr = "04/05/2010";
Date dateObj=null;
SimpleDateFormat curFormater = new SimpleDateFormat("dd/mmm/yyyy");
try
{
dateObj = curFormater.parse(dateStr);
}
catch (java.text.ParseException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
request.addProperty("dtFrom",dateObj);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.setOutputSoapObject(request);
envelope.dotNet = true;
try
{
HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
androidHttpTransport.call(SOAP_ACTION, envelope);
if (envelope.getResponse() != null)
{
if (envelope.bodyIn instanceof SoapFault)
{
String str = ((SoapFault) envelope.bodyIn).faultstring;
Log.i("", str);
}
else
{
SoapObject resultsRequestSOAP = (SoapObject) envelope.bodyIn;
Log.d("WS", String.valueOf(resultsRequestSOAP));
}
};
}
catch (Exception e)
{
Log.d("WS","sss");
}
}
When i change web method(something with out date it work),I get response in log) But when is this way with date i just get catch ("sss" ) in log,i debug and find that it brake on:
androidHttpTransport.call(SOAP_ACTION, envelope);
But i not find anything about that in log except catch that i set...
For me this looks like the dateObj which you want to give to the webservice can not be parsed, thats why the exception occurre at this line:
androidHttpTransport.call(SOAP_ACTION, envelope);
But as your formatter has this format:
SimpleDateFormat curFormater = new SimpleDateFormat("dd/mmm/yyyy");
Maybe the (three!)"mmm" are causing the error?? I am pretty sure this will produce something like e.g. "Feb" for February and so on.. (e.g. "11/Feb/2014"):
Try something like:
SimpleDateFormat curFormater = new SimpleDateFormat("dd/mm/yyyy");
or
SimpleDateFormat curFormater = new SimpleDateFormat("dd/MM/yyyy");
Btw, to avoid localisation and interoperability issues, i often use DateTime objects accurately formatted to Strings for giving that objects over to WebService. Because many times i had problems by interoperability between e.g. .asmx Webservice and J2EE web service (e.g. the range of DateTime is not the same for J2EE and .NET and if it's a null/nil value you also run in troubles).

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.

Can I write code with an expiration date?

I just had this idea for something that I'd love to be able to use:
Let's say I have to fix a bug and I decide to write an ugly code line that fixes the immediate problem - but only because I promise myself that I will soon find the time to perform a proper refactoring.
I want to be able to somehow mark that code line as "Expired in" and add a date - so that if the code is compiled some time after that date there will be a compilation error/warning with a proper message.
Any suggestions? It must be possible to perform - maybe using some complicated #IF or some options in visual studio?
I'm using VS 2005 - mainly for C#.
Mark the code with the System.ObsoleteAttribute attribute, you'll get a compiler warning, which will nag you to fix the code
[Obsolete("You've an ugly hack here")]
public void MyUglyHack()
{
...
}
Alternatively . . .
Write your own attribute, passing it an expiration date on the constructor, in the constructor throw an exception if DateTime.Now >= expirationDate.
The compile will fail until you fix the code (or more likely increase the expiration date, or far more likely you just remove the Attribute.
oooohhh - this is 'orrible. try this for a giggle:
[AttributeUsage(AttributeTargets.All)]
public class BugExpiryAttribute : System.Attribute
{
// don't tell 'anyone' about this hack attribute!!
public BugExpiryAttribute(string bugAuthor, string expiryDate)
{
DateTime convertedDate = DateTime.Parse(expiryDate);
Debug.Assert(DateTime.Now <= convertedDate,
string.Format("{0} promised to remove this by {1}",
bugAuthor, convertedDate.ToString("dd-MMM-yyyy")));
}
}
then, decorate your method/class etc:
[BugExpiryAttribute("Jack Skit", "2011-01-01")]
public static void Main(string[] args)
{
...
}
... nasty :-)
[DISCLAIMER] - created in the name of academic interest, not production code finese!!
[edit] - just to clarify, code compiled and in production will continue to run on/after the 'bugExpriryDate'. only once the code is run in the compiler (on/after the date), will the warning message be raised (debug.assert). just thought it worth making that distinction - cheers MartinStettner.
[caveat] - if used in classes/methods etc would need to be read via reflection. however (and this is interesting) will work straight off in the compiler if used on sub Main(). how strange!! (thanks for the nod Hans...)
I think this is the reason Visual Studio has a Task List. Add the comment:
\\ TODO: Fix this spaghetti by 01APR11
and it will show up like this
.
the keywords are configurable from the options
You could write comment lines in the form
// Expires on 2011/07/01
and add a prebuild step which does a solution-wide replace of these lines by something like
#error Code expired on 2011/07/01
for all lines that contain a date before the current day. For this prebuild step you would need to write a short program (probably using regular expressions and some date comparision logic)
This step could also be performed by a VS macro, which allows for easier access to all files fo the solution but has the disadvantage that it must be installed and run on all VS installations where your project is compiled.
One more option if you have unit tests for your code you can time bomb the tests that verifies your fix. This way you don't introduce strange checks in your production code.
Also I think the best option if you have to put in hack (you've probably already spent enough time looking at it to fix properly... but still want a hack there) than open bug/create task/work item (whatever you use to track future work) and decide if you want to fix it later.
Well it doesn't do exactly what you're asking for but you could use a Debug.Assert() method call which would alert you (in Debug only) that the code has expired. One benefit would be that it wouldn't inadvertently affect your production code (compilation or execution) but would be sufficiently annoying in Debug for you to want to correct it.
// Alert the developer after 01/07/2011
Debug.Assert(Date.Now < new DateTime(2011, 7, 1))
With .NET 6+ this is quite simple when a source generator is used:
Source generator
[Generator]
public class ObsoleteFromDateSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var currentDate = DateTime.Now.Date;
ImmutableArray<AttributeSyntax> attributes = context.Compilation
.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes())
.Where((d) => d.IsKind(SyntaxKind.Attribute))
.OfType<AttributeSyntax>()
.Where(d => d.Name.ToString() == SourceGeneratorConstants.ObsoleteFromDateAttributeName)
.ToImmutableArray();
foreach (var attribute in attributes)
{
try
{
var semanticModel = context.Compilation.GetSemanticModel(attribute.SyntaxTree);
var argumentDate = attribute.ArgumentList?.Arguments.FirstOrDefault()?.Expression;
var argumentMessage = attribute.ArgumentList?.Arguments.Skip(1).FirstOrDefault()?.Expression;
if (argumentDate != null)
{
var date = DateTime.ParseExact(semanticModel.GetConstantValue(argumentDate).ToString(), SourceGeneratorConstants.ObsoleteFromDateAttributeDateFormat, CultureInfo.InvariantCulture);
string? message = null;
if (argumentMessage is not null
&& semanticModel.GetConstantValue(argumentMessage) is Optional<object> tmp
&& tmp.HasValue)
{
message = tmp.Value?.ToString();
}
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ObsoleteFromDate(message, /*isWarning:*/ currentDate < date), attribute.GetLocation());
context.ReportDiagnostic(diagnostic);
}
else
{
throw new ArgumentNullException(paramName: "date");
}
}
catch (Exception ex)
{
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ObsoleteFromDateError(ex.Message), attribute.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
Some constants
public static class SourceGeneratorConstants
{
public const string ObsoleteFromDateAttributeName = "ObsoleteFromDate";
public const string ObsoleteFromDateAttributeDateFormat = "yyyy-MM-dd";
}
Build diagnostics
public static class DiagnosticDescriptors
{
public const string Category = "BNX";
public const string ErrorTitle = "Source code error";
public static readonly Func<string?, bool, DiagnosticDescriptor> ObsoleteFromDate = (message, isWarning) => new DiagnosticDescriptor(
id: $"{Category}{SourceGeneratorConstants.ObsoleteFromDateAttributeName}",
title: "Obsolete code",
messageFormat: message ?? "Obsolete code, please review",
category: Category,
defaultSeverity: isWarning ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
isEnabledByDefault: true);
public static readonly Func<string?, DiagnosticDescriptor> ObsoleteFromDateError = (message) => new DiagnosticDescriptor(
id: $"{Category}{SourceGeneratorConstants.ObsoleteFromDateAttributeName}",
title: ErrorTitle,
messageFormat: $"Unable to parse {SourceGeneratorConstants.ObsoleteFromDateAttributeName} attribute because of error: {message} Expecting the following syntax: [{SourceGeneratorConstants.ObsoleteFromDateAttributeName}(\"{SourceGeneratorConstants.ObsoleteFromDateAttributeDateFormat}\", \"message\")]",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
A custom attribute
/// <summary>
/// Triggers a build error at and after a specific system date. Source generators must be included in the project.
/// </summary>
public class ObsoleteFromDateAttribute : Attribute
{
public const string DateFormat = "yyyy-MM-dd";
/// <summary>
/// Build error message.
/// </summary>
public string Message { get; }
/// <summary>
/// System date at and after which the build error should occur.
/// </summary>
public string Date { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ObsoleteFromDateAttribute"/> class.
/// </summary>
/// <param name="date">Required. System date at and after which the build error should occur. Expected format: <see cref="DateFormat"/></param>
/// <param name="message">Optional. Build error message.</param>
public ObsoleteFromDateAttribute(string date, string message): base()
{
Date = date;
Message = message;
}
}
And finally a test:
// The following should result in a build error
[ObsoleteFromDate("2000-01-01", "We are waiting for some Jira thing 1 to complete")]
public class ObsoleteClass1
{
}
// The following should result in a build warning
[ObsoleteFromDate("3000-01-01", "We are waiting for some Jira thing 2 to complete")]
public class ObsoleteClass2
{
}
Without controlling the compiler (possible in the 5.0 timeframe with compiler as a service?), you are not going to have your code expire. You can mark the code as deprecated, or use the Obsolete attribute, or similar, to fire off a warning, but people can ignore warnings (many devs I have met have not learned the rule that warnings are errors).
I think it is a lot of work to try to protect people from themselves. It is even harder when you are protecting them from themselves in the future. Mark the code as a kludge and leave it at that.
Instead of embedding a time bomb, perhaps consider applying a BUGBUG: comment?
Rather than forcing you or someone else to fix code that may be kind of unsightly but works as expected down the road, you can just do a solution-wide search and find the ugly bits when you decide it's time to get down and refactor the really ugly stuff.
Track it in a bug instead. Then it can be properly scheduled and prioritized with other refactoring work.
TODO comments in code can have a tendency to be lost and forgotten. Throwing a compiler error after a particular date will likely lead to that date being pushed forward, or the comment/attribute removed.
I hope i can help with this. take 2 datetimepicker on tool box. And just convert 1 datetimepicker.
private void expired()
{
DateTime expired = DateTime.Parse(Convert.ToDateTime(datetimepicker1.Text).ToString());
DateTime compare = DateTime.Parse(Convert.ToDateTime(datetimepicker2.Text).ToString());
if(expired < compare)
{
MessageBox.Show("This product is expired!");
}
else
}
MessageBox.Show("This product is not expired");
{
}
Both TIME and DATE emit strings and, to my knowledge, there is no way to parse them out at the preprocessing stage.
There are a few methods you can easily do in code to ensure that the code at least warns you at run time. Including an assert is one way, putting in a code comment also works, but the way I handle it is through including a doxygen comment with a note explaining that the function contains a hack, bug, or performance issue that needs to be resolved. This ends up getting filtered by many programmers and is easily viewable on the website for myself or other people to fix.

C# Web Service - Works in IE but not Safari

I have a simple webservice, which I want to access via a http post.
The webservice code is show below:
[WebMethod]
public int Insert(string userDate, string DeviceID)
{
bool output;
DateTime date;
output = DateTime.TryParse(userDate, out date);
if (!output)
{
// Throw an error
return -1;
}
int Device;
output = int.TryParse(DeviceID, out Device);
if (!output)
{
// Throw an Error
return -1;
}
UsersDatesBLL BLL = new UsersDatesBLL();
return BLL.Insert(Device, date);
}
I can access the service fine using internet explorer, the results are inserted to the database perfectly simply by calling: CountDownService.asmx/Insert?userDate=24/04/1980&DeviceID=3435
However when testing on Safari and Firefox the service always returns -1
Does anyone know the cause of this? Does Safari encode strings differently to IE?
Regards
Mick
Users can configure their UI language and culture in their browser. The browser passes this information as the Accept-Language HTTP header in requests to your webservice. That information may be used to set the "current culture" of the ASP.NET session that handles the request. It is available as the static property CultureInfo.CurrentCulture.
DateTime.TryParse will use that "current culture" to figure out which of the many datetime string formats it should expect - unless you use the overload where you explicitly pass a culture as the IFormatProvider. Apparently the browsers you are testing with are configured differently, so ASP.NET expects different datetime formats from each. If you want the datetimes to be parsed independently from the browser settings, then you should use CultureInfo.InvariantCulture as the format provider.
The first thing I would do as a debugging measure (assuming you can't just run a debugger on the server code) would be to make all three return paths return different values. That way you're not left guessing whether it was the DateTime.TryParse() call, the int.TryParse() call, or the BLL.Insert() call that failed.
If BLL.Insert() returns a -1 on failure, then I would change the first -1 to -3 and the second to -2. Like so:
output = DateTime.TryParse(userDate, out date);
if (!output)
{
// Throw an error
return -3; // ***** Changed this line
}
int Device;
output = int.TryParse(DeviceID, out Device);
if (!output)
{
// Throw an Error
return -2; // ***** Changed this line
}
I know it doesn't exactly answer the question, but it would at least help you track down which part is failing.
You are using the current culture to parse your DateTime and int values. The first thing to check is whether your various browsers are all configured to send the same culture to the web server in their HTTP request headers.
As a matter of style, you should avoid using culture-dependent formats for URL parameters. The most appropriate format to use is the fixed XML Schema format, like this:
[WebMethod]
public int Insert(string userDate, string DeviceID) {
DateTime date;
int device;
try {
date = XmlConvert.ToDateTime(userDate, XmlDateTimeSerializationMode.Local);
device = XmlConvert.ToInt32(DeviceID);
} catch (Exception) {
// Throw an error
return -1;
}
UsersDatesBLL BLL = new UsersDatesBLL();
return BLL.Insert(device, date);
}
And call it like this:
CountDownService.asmx/Insert?userDate=1980-04-24&DeviceID=3435
Another alternative is to use strongly-typed parameter values and let ASP.NET do the parsing for you, e.g.
[WebMethod]
public int Insert(DateTime userDate, int DeviceID) {
UsersDatesBLL BLL = new UsersDatesBLL();
return BLL.Insert(DeviceID, userDate);
}
DISCLAIMER - I have not tried this alternative myself, but it's worth a look, because it will make your life easier if you don't have to put parsing code into every web method.

Categories

Resources