私たちのソリューション パート 1: Entity Framework
Entity Framework がDateTime
データベースから値を取得すると、値が に設定されDateTimeKind.Unspecified
ます。つまり、ローカルでも UTC でもありません。具体的には、日付を としてマークしたかったのですDateTimeKind.Local
。
これを実現するために、エンティティ クラスを生成する Entity Framework のテンプレートを微調整することにしました。単純なプロパティである日付の代わりに、バッキング ストアの日付を導入し、プロパティ セッターを使用して、日付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)));
}
... と ...
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)));
}
}
これにより、やや醜い 1 行のセッターが作成されますが、仕事は完了します。ヘルパー関数を使用して、日付を次の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;
}
}
私たちのソリューション パート 2: IQueryable フィルター
次の問題は、Breeze がコントローラー アクションwhere
に句を適用するときに UTC 日付を渡すことでした。IQueryable
Breeze、Web API、および Entity Framework のコードを見直した後、コントローラー アクションの呼び出しをインターセプトし、UTC 日付QueryString
をローカル日付に置き換えることが最善の選択肢であると判断しました。
次のようなコントローラー アクションに適用できるカスタム属性を使用してこれを行うことにしました。
[UseLocalTime]
public IQueryable<Product> Products()
{
return _dc.Context.Products;
}
この属性を実装したクラスは次のとおりです。
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);
}
}
}
私たちのソリューション パート 3: Json
これはもっと物議を醸すかもしれませんが、私たちのウェブアプリのオーディエンスも完全にローカルです:)。
クライアントに送信される Json には、デフォルトでローカル タイムゾーンの日付/時刻が含まれるようにしました。また、クライアントから受け取った Json の日付をローカル タイムゾーンに変換したいと考えていました。これを行うために、カスタムを作成し、JsonLocalDateTimeConverter
Breeze がインストールする Json コンバーターを交換しました。
コンバーターは次のようになります。
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;
}
}
最後に、上記のコンバーターをインストールするために、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;
}
}
それはそれについてです。すべてのコメントと提案を歓迎します。