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
}
Related
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.
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.
I am having trouble finding the published date. I working the Umbraco.Core.Models.IPublishedContent interface, which doesn't seem to have a published date, only a created and updated date.
All the docs I found on the interwebs, suggest using Document(id), then Document.ReleasedDate, but this now marked obsolete. It suggests to use the ReleaseDate in Umbraco.Core.Models.Content class.
What am I missing?
Use the UpdateDate on IPublishedContent. That date is always updated when you publish content.
The ReleaseDate that you mention is used to set a future date and time for when a specific content item should be published (automatically). So that is not the date you are after. When a release date is set the UpdateDate will also be updated with this date once the item is published.
Umbraco content items don't have a built-in property to indicate when they were first published.
If you want a reliable indication of when content was actually published, the best option is to add a custom property to your document type. You can then add an event handler to your application, which updates the property to the current date when it is first published:
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Services;
namespace YourNamespace
{
/// <summary>
/// Updates the publishedDate property when content is first published
/// </summary>
public class UpdatePublishDateEventHandler : ApplicationEventHandler
{
protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Published += ContentService_Published;
}
void ContentService_Published(Umbraco.Core.Publishing.IPublishingStrategy sender, Umbraco.Core.Events.PublishEventArgs<Umbraco.Core.Models.IContent> e)
{
const string publishedDateKey = "publishedDate";
var contentService = ApplicationContext.Current.Services.ContentService;
foreach (var content in e.PublishedEntities.Where(x => x.HasProperty(publishedDateKey)))
{
var existingValue = content.GetValue(publishedDateKey);
if (existingValue == null)
{
content.SetValue(publishedDateKey, DateTime.Now);
contentService.SaveAndPublishWithStatus(content, raiseEvents: false);
}
}
}
}
}
Umbraco automatically scans for and activates classes that inherit from ApplicationEventHandler at start-up, so you simply need to add the above class to your project.
If you are using Umbraco 7 take a look at the ReleaseDate property on the Umbraco.Core.Models.IContent interface. Apparently it "Gets or sets the date the Content should be released and thus be published".
I think you should probably be using the".Created" date. As this will be the date the article was originally published.
Alternatively you could use a custom DateTime property on your DocType and use that as the publish date by retrieving it as follows:
YourNodeObject.GetPropertyValue<DateTime>("customPropertyAliasHere");
Regards
I'm working on an application (a web application, asp.net and c#) which is datetime-dependent, so, based on the current date, it will launch forms for the logged user to fill in.
I've been thinking about how we're going to simulate real usage of the application, for debugging and testing purposes.
So I'm talking about replacing all those:
DateTime currentDate = DateTime.Now;
with something like:
DateTime currentDate = MyDateClass.GetCurrentDate();
And then I'll have a class:
public class MyDateClass
{
private DateTime _currentDate;
public DateTime GetCurrentDate()
{
// get the date, which may be different from DateTime.Now
return _currentDate;
}
public void SetCurrentDate(DateTime newCurrentDate)
{
// set the date to the value chosen by the user
_currentDate = newCurrentDate;
}
}
allowing me to set the current data, by invoking the SetCurrentDate method, for example, in the code-behind of a link button and a calendar input.
Question is... how should I exactly store the DateTime variable, throughout all the application? I can't work with the session in this class, right? Should I work with the Thread?
Well, ideas are appreciated :) Thanks in advance for your suggestions!
Some updating on my question:
I ran into this post: What's a good way to overwrite DateTime.Now during testing?
Just like you provided here with your answers (thank you!), great tips for good code organization, for both development and testing purposes, that I will definitely be considering in the time to follow.
I still have the same question though: how will I "hold" the datetime value?
Right now, I went with creating a single cell table in the data base to maintain "my" datetime value.
I have a static class with a GetCurrentDate and a SetCurrentDate function:
public static class DateManagement
{
public static DateTime GetCurrentDate()
{
// return DateTime.Now;
// OR:
// ...
// SqlCommand cmd = new SqlCommand("select Date from CurrentDate", conn);
// ...
}
public static void SetCurrentDate(DateTime newDate) // used only in debugging
{
// ...
// string insertStr = #"update CurrentDate set Date = '" + date + "'";
// SqlCommand cmd = new SqlCommand(insertStr, conn);
// ...
}
}
However, storing the date in the database, with a table created and used just for debugging, doesn't seem like an elegant solution...
Create an interface:
public interface ITimeProvider
{
DateTime Now { get; }
}
Then you can create two subclasses - one for production usage which just returns the current time using DateTime.Now, and another where you can manually set the time for testing purposes. Use dependency injection to provide the appropriate implementation.
Can you use Rhino Mocks (or similar) to spoof the current date time in your unit tests, rather than writing code just for testing purposes?
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.