Bre*_*dan 12 timezone datetime entity-framework asp.net-web-api breeze
我写这篇文章是为了收集关于我们方法的评论,希望能帮助别人(和我的记忆).
DateTime没有时区信息的数据类型.where子句)中.想象一下Breeze控制器直接将数据库中的表作为IQueryable公开.Breeze客户端将以UTC格式将任何日期过滤器(where)子句传递给服务器.实体框架将忠实地使用这些日期来创建SQL查询,完全不知道数据库表日期在我们的本地时区.对我们而言,这意味着结果在12到13个小时之间,偏离了我们想要的结果(取决于夏令时).我们的目标是确保我们的服务器端代码(和数据库)始终在我们的本地时区中使用日期,并且所有查询都返回所需的结果.
Bre*_*dan 15
当Entity Framework DateTime从数据库获取值时,它将它们设置为DateTimeKind.Unspecified.换句话说,既不是本地的也不是UTC的.我们特别希望将日期标记为DateTimeKind.Local.
为实现这一目标,我们决定调整实体框架的生成实体类的模板.相反,我们的日期是一个简单的属性,我们推出了后备存储日期和使用属性setter作出之日起Local,如果它是Unspecified.
在模板(.tt文件)中我们替换了......
public string Property(EdmProperty edmProperty)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}
Run Code Online (Sandbox Code Playgroud)
...... ......
public string Property(EdmProperty edmProperty)
{
// Customised DateTime property handler to default DateKind to local time
if (_typeMapper.GetTypeName(edmProperty.TypeUsage).Contains("DateTime")) {
return string.Format(
CultureInfo.InvariantCulture,
"private {1} _{2}; {0} {1} {2} {{ {3}get {{ return _{2}; }} {4}set {{ _{2} = DateKindHelper.DefaultToLocal(value); }}}}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
} else {
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}
}
Run Code Online (Sandbox Code Playgroud)
这创造了一个相当丑陋的单线制定者,但它完成了工作.它确实使用辅助函数将日期默认为Local如下所示:
public class DateKindHelper
{
public static DateTime DefaultToLocal(DateTime date)
{
return date.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(date, DateTimeKind.Local) : date;
}
public static DateTime? DefaultToLocal(DateTime? date)
{
return date.HasValue && date.Value.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(date.Value, DateTimeKind.Local) : date;
}
}
Run Code Online (Sandbox Code Playgroud)
下一个问题是Breeze在将where句子应用于我们的IQueryable控制器操作时传递UTC日期.在查看了Breeze,Web API和Entity Framework的代码之后,我们决定最好的选择是拦截对我们的控制器操作的调用,并在QueryString本地日期中交换UTC 日期.
我们选择使用我们可以应用于控制器操作的自定义属性来执行此操作,例如:
[UseLocalTime]
public IQueryable<Product> Products()
{
return _dc.Context.Products;
}
Run Code Online (Sandbox Code Playgroud)
实现此属性的类是:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Text.RegularExpressions;
using System.Xml;
namespace TestBreeze.Controllers.api
{
public class UseLocalTimeAttribute : ActionFilterAttribute
{
Regex isoRegex = new Regex(@"((?:-?(?:[1-9][0-9]*)?[0-9]{4})-(?:1[0-2]|0[1-9])-(?:3[0-1]|0[1-9]|[1-2][0-9])T(?:2[0-3]|[0-1][0-9]):(?:[0-5][0-9]):(?:[0-5][0-9])(?:\.[0-9]+)?Z)", RegexOptions.IgnoreCase);
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// replace all ISO (UTC) dates in the query string with local dates
var uriString = HttpUtility.UrlDecode(actionContext.Request.RequestUri.OriginalString);
var matches = isoRegex.Matches(uriString);
if (matches.Count > 0)
{
foreach (Match match in matches)
{
var localTime = XmlConvert.ToDateTime(match.Value, XmlDateTimeSerializationMode.Local);
var localString = XmlConvert.ToString(localTime, XmlDateTimeSerializationMode.Local);
var encoded = HttpUtility.UrlEncode(localString);
uriString = uriString.Replace(match.Value, encoded);
}
actionContext.Request.RequestUri = new Uri(uriString);
}
base.OnActionExecuting(actionContext);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这可能更具争议性,但我们的网络应用程序受众也完全是本地的:).
我们希望Json默认发送到客户端以包含我们当地时区的日期/时间.此外,我们希望从客户收到的Json中的任何日期都转换为我们当地的时区.为此,我们创建了一个自定义JsonLocalDateTimeConverter并换出了Json转换器Breeze安装.
转换器看起来像这样:
public class JsonLocalDateTimeConverter : IsoDateTimeConverter
{
public JsonLocalDateTimeConverter () : base()
{
// Hack is for the issue described in this post (copied from BreezeConfig.cs):
// http://stackoverflow.com/questions/11789114/internet-explorer-json-net-javascript-date-and-milliseconds-issue
DateTimeFormat = "yyyy-MM-dd\\THH:mm:ss.fffK";
}
// Ensure that all dates go out over the wire in full LOCAL time format (unless date has been specifically set to DateTimeKind.Utc)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime)
{
// if datetime kind is unspecified then treat is as local time
DateTime dateTime = (DateTime)value;
if (dateTime.Kind == DateTimeKind.Unspecified)
{
dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
}
base.WriteJson(writer, dateTime, serializer);
}
else
{
base.WriteJson(writer, value, serializer);
}
}
// Ensure that all dates arriving over the wire get parsed into LOCAL time
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var result = base.ReadJson(reader, objectType, existingValue, serializer);
if (result is DateTime)
{
DateTime dateTime = (DateTime)result;
if (dateTime.Kind != DateTimeKind.Local)
{
result = dateTime.ToLocalTime();
}
}
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
最后为了安装上面的转换器,我们创建了一个CustomBreezeConfig类:
public class CustomBreezeConfig : Breeze.WebApi.BreezeConfig
{
protected override JsonSerializerSettings CreateJsonSerializerSettings()
{
var baseSettings = base.CreateJsonSerializerSettings();
// swap out the standard IsoDateTimeConverter that breeze installed with our own
var timeConverter = baseSettings.Converters.OfType<IsoDateTimeConverter>().SingleOrDefault();
if (timeConverter != null)
{
baseSettings.Converters.Remove(timeConverter);
}
baseSettings.Converters.Add(new JsonLocalDateTimeConverter());
return baseSettings;
}
}
Run Code Online (Sandbox Code Playgroud)
就是这样.欢迎提出所有意见和建议.