WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

日付時刻と JSON 文字列

by WebSurfer 2017年8月27日 15:01

JavaScript の Date オブジェクトや .NET Framework の DateTime オブジェクトの JSON 文字列への変換について調べましたので備忘録として書いておきます。(逆の、下の表の JSON 文字列を Newtonsoft.Json および System.Text.Json 名前空間の JsonSerializer クラスを使って DateTime オブジェクトにデシリアライズする話は別の記事「JSON 文字列の日付時刻のデシリアライズ」に書きましたので興味があれば見てください)

ASP.NET ajax によるパース結果

JSON の紹介よると、その記事に書いてある value に直接設定できるのは string, number, object, array, true, false, null だけということですので、JavaScript の Date オブジェクトや .NET Framework の DateTime オブジェクトは string に変換して JSON 文字列に設定せざるを得ません。

変換は自力で行わなくても、例えば、JavaScript なら JSON.stringify() メソッド、.NET Framework なら DataContractJsonSerializer クラスを利用すれば自動的に変換してくれます。

その他、JavaScriptSerializer クラス、ASP.NET Web サービス、WCF、ASP.NET Web API でも DateTime 型を自動的に文字列に変換してくれますが、それぞれどのような結果になるかを以下にまとめておきます。

No. 方法 結果
(1) JSON.stringify() 2017-02-01T03:15:45.000Z
(2) DataContractJsonSerializer \/Date(1503727168573+0900)\/
(3) WCF 上記 (2) の結果と同じ
(4) JavaScriptSerializer \/Date(1030806000000)\/
(5) ASP.NET Web サービス 上記 (4) の結果と同じ
(6) ASP.NET Web API (注) 2017-08-26T15:39:32.6330349+09:00

(注): Newtonsoft.Json と同じ。.NET Framework 版および Core v2.x 版の ASP.NET Web API はシリアライザに Newtonsoft.Json を使っています。ASP.NET Core 3.x 以降の Web API は System.Text.Json 名前空間JsonSerializer クラスがシリアライズに用いられるようになりました。なお、シリアライズの結果は (6) と同じになります。

以下に上記 (1) ~ (6) のそれぞれの検証方法や備忘録として残しておいた方がよさそうな情報を書いておきます。

(1) JSON.stringify() メソッド

ECMAScript 2017 Language Specification (ECMA-262, 8th edition, June 2017) の 20.3.1.16 Date Time String Format のセクションによると、ISO 8601 Extended Format 即ち:

YYYY-MM-DDTHH:mm:ss.sssZ

という形式に変換することになっているそうです。

全てのブラウザで確認したわけではありませんが、IE11, Chrome 60.0.3112.113, Firefox 55.0.3 では確かにその通りになるのを確認しました。

なお、JavaScript の Date オブジェクトはローカルタイムとして扱われまずが、JSON.stringify() メソッドで文字列に変換すると UTC になるので注意してください(YYYY-MM-DDTHH:mm:ss.sssZ の末尾の Z は UTC であることを意味します)。

例えば、以下のコードを実行すると、

var dateTime = new Date(2017, 1, 1, 12, 15, 45);
var dateObject = { DateTime: dateTime };
var dateJson = JSON.stringify(dateObject);

dateJson は {"DateTime":"2017-02-01T03:15:45.000Z"} という JSON 文字列になります。

Date コンストラクタの引数で月を表す整数値は 0 (1月) から 11 (12月) なので、1 ⇒ 02 という結果になっています。時間 12 が 03 になるのは東京ローカルタイムが UTC に変換されるためです。

(2) DataContractJsonSerializer クラス

DataContractJsonSerializer クラスを使うと、別の記事「w2ui Grid」に書きましたように、.NET Framework の DateTime 型は:

\/Date(1503727168573+0900)\/

のような文字列に変換されます。

この例の最初の数字 1503727168573 は UTC 1970 年 1 月 1 日午前 0 時からカウントしたミリ秒、+0900 はオプションで、ローカルタイムの UTC からの時差だそうです。

詳しくは以下の MSDN ライブラリの記事「スタンドアロン JSON のシリアル化」の「高度な情報 / DateTime ワイヤ形式」のセクションの説明を見てください。

(3) WCF

先の記事「WCF と jQuery AJAX」の CarService.svc.cs のコードに以下のメソッドを追加して試してみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;

namespace WebApplication1
{
    // web.config で aspNetCompatibilityEnabled="true" と設定され
    // ているので [AspNetCompatibilityRequirements(...)] が必要
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = 
        AspNetCompatibilityRequirementsMode.Allowed)]
    public class CarService
    {
        // ・・・中略・・・

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Wrapped)]
        public DateTimeJsonTest GetToday()
        {
            DateTimeJsonTest today = new DateTimeJsonTest();
            today.Today = DateTime.Now;
            return today;
        }
    }

    // ・・・中略・・・

    [DataContract]
    public class DateTimeJsonTest
    {
        [DataMember]
        public DateTime Today { set; get; }
    }
}

ブラウザ から上記コードの GetToday を要求すると以下のような JSON 文字列が返されます:

{"GetTodayResult":{"Today":"\/Date(1503727168573+0900)\/"}}

すなわち、上記「(2) DataContractJsonSerializer クラス」の場合と同じになります。(内部的に DataContractJsonSerializer クラスを使って変換しているのであろうと思います。未確認ですが・・・)

(4) JavaScriptSerializer クラス

ASP.NET MVC の Controller クラスの Json メソッドの内部で .NET Framework のオブジェクトを JSON 文字列にシリアライズするために JavaScriptSerializer クラスが用いられているそうです。

先の記事「jQuery.ajax で JSONP」に JavaScriptSerializer クラスを使ってシリアル化を行っている例がありますので、これに少し手を加えて検証してみました。

その記事のコードを見てください。Controller の Jsonp アクションメソッドでは Address テーブルから AddressID, AddressLine1, City フィールドのデータを取得して匿名クラスのオブジェクトを作り、それを JavaScriptSerializer ク��スの Serialize メソッドで JSON 文字列にシリアライズしています。

Address テーブルには ModifiedDate という datetime 型のフィールドがありますのでそれを匿名クラス address に追加してシリアライズしてみました。

Serialize(address) の結果は以下のような文字列となります。

{"AddressID":25,"AddressLine1":"9178 Jumping St.","City":"Dallas","ModifiedDate":"\/Date(1030806000000)\/"}

即ち、DateTime 型の変換結果は上記「(2) DataContractJsonSerializer クラス」とほぼ同じで、オプションのローカルタイムの UTC からの時差(上の例では +0900)が省略された形になっています。

(5) ASP.NET Web サービス

先の記事「ASP.NET AJAX と Web サービス」のコードに以下のメソッドを追加して試してみました。

<%@ WebService Language="C#" Class="CarService" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Collections.Generic;
using System.Linq;

// ・・・中略・・・

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class CarService : WebService
{
    // ・・・中略・・・

    [WebMethod]
    public DateTimeJsonTest GetToday()
    {
        DateTimeJsonTest today = new DateTimeJsonTest();
        today.Today = DateTime.Now;
        return today;
    }
}

public class DateTimeJsonTest
{
    public DateTime Today { set; get; }
}

上記 GetToday() メソッドを、要求ヘッダに Content-Type: application/json を追加して POST 要求すると(注)以下のような JSON 文字列が返されます:

{"d":{"__type":"DateTimeJsonTest","Today":"\/Date(1503712714770)\/"}}

(注)ASP.NET Web サービスの制約上 POST 要求しないとエラーになります。Content-Type を指定しない、または application/x-www-form-urlencoded とすると XML 形式で返されます。XML の場合は形式は上記とは異なり 2017-08-26T10:36:27.0809765+09:00 のようになります。

即ち、上記「(4) JavaScriptSerializer クラス」と同じ形式の文字列に変換されます(+0900 も同じく省略されています)。

ASP.NET AJAX を利用すると、\/Date(1503712714770)\/ という形式の文字列は、Date 型の JavaScript オブジェクトに自動的に変換されます。具体例は以下の通りです。

記事「ASP.NET AJAX と Web サービス」の「(2) JavaScript (097_ASPNETAjaxAndWebService.js)」のセクションに記載されているコードを以下のように書き換えます。

var serviceProxy;

// プロキシの初期化とコールバック関数の設定
function pageLoad() {
    serviceProxy = new CarService();
    serviceProxy.set_defaultSucceededCallback(Succeeded);
    serviceProxy.set_defaultFailedCallback(Failed);
}

// ボタンクリックで呼び出されるサービスメソッド  
function getToday() {
    serviceProxy.GetToday();
}

// AJAX 通信が成功したときに呼び出され、戻ってきたデータ
// を処置するコールバック関数。
// 引数 data は JSON 文字列ではなく、DateTimeJsonTest 型
// オブジェクト。それから Today プロパティで取得できるの
// は、文字列ではなく Date 型に変換済みのオブジェクト。
function Succeeded(data) {
    $('#output').empty();
    var now = data.Today;  // now は Date 型オブジェクト
    $('#output').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
    );
}

// ・・・以下略・・・

上の getToday() メソッドを「(3) apsx ページ (097_ASPNETAjaxAndWebService.aspx) 」のセクションに記載されているコードと同等な aspx ページから呼び出すと、Date 型オブジェクトが上記コードの now に取得でき、この記事の一番上の画像のとおり表示されます。

MSDN ライブラリの記事「スタンドアロン JSON のシリアル化」の「高度な情報 / DateTime ワイヤ形式」のセクションの説明によると、"UTC からの時差を示す部分(例: +0900)が指定されていない場合、時刻は UTC として逆シリアル化されます" とありますが、上記コードの結果はローカル時刻になります。

ASP.NET AJAX に代えて jQuery ajax を使う場合は自動的に Date オブジェクトには変換してくれませんので、自力でコードを書いて変換することになります。

具体例は以下の通りです。数字の部分を抜き出して JavaScript の Date コンストラクタの引数として渡し、Date オブジェクトを生成すれば ASP.NET AJAX と同じ結果が得られます。

function getDateByJqueryAjax() {
  $.ajax({
    type: "POST",

    url: "097_jQueryAjaxAndWebService.asmx/GetToday",

    contentType: "application/json; charset=utf-8",

    success: function (data) {
      if (data.hasOwnProperty('d')) {
          data = data.d;
      }
      $('#output').empty();
      var re = /^\/Date\(([0-9]+)\)\/$/;
      var results = re.exec(data.Today);
      var now = new Date(Number(results[1]));
      $('#output').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
      );
    },

    error: function (jqXHR, textStatus, errorThrown) {
      $('#output').text('textStatus: ' + textStatus +
                  ', errorThrown: ' + errorThrown);
    }
  });
}

(6) ASP.NET Web API (Newtonsoft.Json と同じ)

Visual Studio 2010 Professional のテンプレートを使って自動生成される ASP.NET Web API アプリのコードに、以下のメソッドを追加して試してみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApi1.Models;

namespace WebApi1.Controllers
{
    public class HeroesController : ApiController
    {
        public DateTimeJsonTest Get()
        {
            DateTimeJsonTest today = new DateTimeJsonTest();
            today.Today = DateTime.Now;
            return today;
        }

        // ・・・中略・・・
    }
}

jQuery ajax を利用して上の Get() メソッドを呼び出すと以下のような JSON 文字列が返されます:

{"Today":"2017-08-26T15:39:32.6330349+09:00"}

DateTime 型の文字列への変換は上の「(5) ASP.NET Web サービス」の場合の XML 形式の応答の場合と同じです。

上にも書きましたが、ASP.NET Web API はシリアライザに Newtonsoft.Json を使っています。この形式は Newtonsoft.Json 独自なのか、ISO 8601 などに準拠しているのかは調べ切れてません。

なお、この形式で、JavaScript の Date 型へのパースは IE11, Chrome 60.0.3112.113, Firefox 55.0.3 のいずれも可能です。例えば、jQuery ajax を使うと、success オプションに設定したコールバックの引数に上の JSON 文字列を JavaScript オブジェクトに変換して代入してくれますので、それから以下のようにして Date オブジェクトにパースできます。

success: function (data, textStatus, jqXHR) {
    var now = new Date(data.Today);
    $('#heroes').append(
        "<p>" + now.getFullYear() + "年" +
        (now.getMonth() + 1) + "月" + now.getDate() + "日" +
        now.getHours() + "時" + now.getMinutes() + "分" +
        now.getSeconds() + "秒</p>"
    );
},

Tags: , ,

JavaScript

IE11 と Referer

by WebSurfer 2017年8月13日 17:02

ASP.NET 4 の Web Forms アプリで、クエリ文字列に日本語を含めて要求をかけた場合、Windows 10 の IE11 と Edge ではポストバックの際 Referer が送信されないという話を書きます。(ASP.NET 4.5 では問題は解消されています。ASP.NET 3.5 以下は未確認です)

Fiddler によるキャプチャ

そもそもの原因は Windows 10 の IE11 や Edge ではなく、ASP.NET 4(3.5 以前も?・・・未確認です)が応答の form 要素に設定する action 属性にあるようです。

例えば、IE11 のアドレスバーに直接以下の URL(クエリ文字列は "日本語" を UTF-8 のパーセントエンコードしたものです)を入力して直接ページを GET 要求したとします。

http://aspnet4site/test.aspx?P1=%e6%97%a5%e6%9c%ac%e8%aa%9e

そうすると、ASP.NET により応答の form 要素の action 属性に設定される URL は以下のようになります。

action="./test.aspx?P1=%u65e5%u672c%u8a9e"

つまり、要求 URL のクエリ文字列の "日本語" は UTF-8 のパーセントエンコーディングなのに、応答の form 要素の action 属性に設定されるのは Unicode のパーセントエンコーディングになってしまいます。そこに問題があるようです。

具体的には下にアップした簡単なサンプルのページ test.aspx で再現できます。

上の画像は、UTF-8 でパーセントエンコーディングした "日本語" をクエリ文字列に含めた URL を IE11 のアドレスバーに直接入力して test.aspx を要求し、表示されたページで 2 回ポストバックを行ったときの Fiddler によるキャプチャ画像です。

上の画像の #1 が初回の要求・応答で、#3 が 1 回目のポストバック、#4 が 2 回目のポストバックの際の要求・応答です。

問題は 2 回目のポストバックのときで、ブラウザからリファラが送信されません。Edge も同様で 2 回目のポストバックの際はリファラは送信されません。

どのような動きになるかと言うと・・・

#1 の初回要求に対して ASP.NET が返す応答の form 要素の action 属性に同じページの URL が設定されますが、URL に含まれるクエリ文字列の "日本語" は Unicode のパーセントエンコーディング %u65e5%u672c%u8a9e となってしまいます。

なので、1 回目のポストバックの時は要求 URL に含まれるクエリ文字列は %u65e5%u672c%u8a9e となります。上の画像の #3 を見てください。

ただし、1 回目のポストバックの際にブラウザから送信されるリファラは、最初の要求の URL(すなわちアドレスバーに直接入力した URL)になります。つまり、クエリ文字列の "日本語は" UTF-8 のパーセントエンコーディングなので問題なくリファラとして送信されます。問題は 2 回目以降です。

2 回目のポストバックの時(上の画像の #4)は、1 回目のポストバックの際の要求 URL(すなわちクエリ文字列の "日本語" が %u65e5%u672c%u8a9e)をリファラとして送信するはずですが、Windows 10 の IE11 と Edge では送信されません。3 回目、4 回目も同様です。

Microsoft の公式文書が見つけられないので想像ですが、Windows 10 の IE11 と Edge ではセキュリティ対策が強化され、URL に UTF-8 として解釈できないコードがあるとリファラを送信しないということではないかと思っています。

ちなみに、Windows 7 の IE11 では %u65e5%u672c%u8a9e となってしまっていてもリファラは送信されるそうです(聞いた話で未検証・未確認)。Chrome 60.0.3112.90, Firefox 54.0.1 では %u65e5%u672c%u8a9e でも送信されることは確認しました。

やはり、URL にはクエリ文字列を含めて ASCII 文字のみ使用することで徹底するのがよさそうです。

サンプルページ test.aspx

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.UrlReferrer != null)
        {
            Label1.Text = Request.UrlReferrer.ToString();
        }
        else
        {
            Label1.Text = "無し";
        }
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <h1>Page B</h1>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    <br />
    UrlReferrer: 
    <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Tags: , ,

ASP.NET

スキャフォールディング機能

by WebSurfer 2017年7月23日 18:41

既存のデータベースから Entity Data Model (EDM) を作り、スキャフォールディング機能を使って Create, Read, Update, Delete (CRUD) 操作を行うための ASP.NET MVC アプリを作る手順を備忘録として書いておきます。

完成した Crate 画面

上の画面は作成した ASP.NET MVC アプリの編集 (Create) 画面を IE11 から呼び出して表示したものです。

この記事では、既存のデータベースとして Microsoft のサンプルデータベース Northwind を使用し、その中の Products テーブルの CRUD 操作を行うことにします。

大まかな手順は以下の通りです。

  1. SQL Server にサンプルデータベース Northwind をアタッチして接続できるようにしておく。(LocalDB も使えます。詳しくは先の記事「LocalDB で Northwind と Pubs を利用」を参照)
  2. Visual Studio Community 2015 のテンプレートを使用して ASP.NET MVC5 プロジェクトを生成。
  3. Visual Studio のデザイナを利用して、Northwind データベースから EDM を生成してプロジェクトに追加する。
  4. 追加した EDM をベースに、スキャフォールディング機能を使って CRUD 操作用の Controller と View を自動生成する。

以下に、Visual Studio で上の操作を行った際に表示された画像を貼って要点を書いておきます。自力では一行もコードを書くことなく CRUD 操作が可能な ASP.NET MVC アプリを自動生成できます。

(1) 新しい項目の追加

新しい項目の追加

プロジェクトに EDM を追加するため、ソリューションエクスプローラーでプロジェクトノードを右クリック⇒[追加(D)]⇒[新しい項目(W)...]をクリック。

(2) ADO.NET Entity Data Model を選択

ADO.NET Entity Data Model を選択

表示された「新しい項目の追加」ダイアログで[ADO.NET Entity Data Model]を選択し[追加(A)]をクリック。

(3) モデルのコンテンツの選択

モデルのコンテンツの選択

既存の SQL Server データベースから EDM を生成するため、[データベースから EF Designer]を選択し[次へ(N)]をクリック。

(4) データ接続の選択

データ接続

接続先の SQL Server データベースの指定、接続文字列等の設定を行うため[新しい接続(C)...]をクリック。

(注:上の画像で DefaultConnection というのは Visual Studio のテンプレートでプロジェクトを生成した時に自動的に設定される ASP.NET Identity 用のユーザー情報のストアに使用されるデータベースです)

(5) 接続のプロパティ設定

接続のプロパティ設定

自分の環境では MySQL がデフォルトで選択されているので、これを SQL Server に変更するため[変更(C)...]をクリック。デフォルトで SQL Server が選択されていれば不要な操作です。

(6) データソースの変更

データソースの変更

今回は SQL Server の既定の(または名前付き)インスタンスに接続するため[Microsoft SQL Server]を選択します。

(7) 接続のプロパティ設定

接続のプロパティ設定

[サーバー名(E)]にサーバー名を入力すると[データベース名の選択または入力(D)]にアタッチ済みのデーターベース一覧が表示されるので、その中から Northwind を選択し[OK]ボタンをクリック。

(注:この例では開発マシンにインストールした SQL Server 2008 Express に接続しています。デフォルトでインストールしたので sqlexpress という名前の名前付きインスタンスになっています)

(8) 接続文字列の確認と web.config への保存

接続文字列の確認と web.config への保存

(4) の画面に戻るので、接続文字列とその名前を確認し[次へ(N)]ボタンをクリック。

(9) データベースオブジェクトと設定の選択

データベースオブジェクトと設定の選択

今回の例では Products テーブルの CRUD 操作を行うので[テーブル]にチェックを入れ[完了(F)]ボタンをクリック。

(10) 生成された EDM

生成された EDM

無事 EDM が生成されると上のような画面が表示されます。ソリューションエクスプローラーの .edmx ファイルの下には DbContext を継承したコンテキストクラス、データベースの各テーブルを表すモデルクラスも自動生成されます。

(11) コントローラーの追加

コントローラーの追加

スキャフォールディング機能を利用して Controller と View を MVC アプリに追加します。ソリューションエクスプローラーで Controller フォルダを右クリック⇒[追加(D)]⇒[コントローラー(T)...]をクリック。

(12) スキャフォールディングを追加

スキャフォールディングを追加

上の手順で生成した EDM をベースに Controller と View を一式生成するには[MVC 5 Controller with Views, using Entiry Framework]を選択し[追加」ボタンをクリックします。

(13) コンテキストクラス、モデルクラスの指定

コンテキストクラス、モデルクラスの指定

(10) で生成されたコンテキストクラス、モデルクラスを指定します。今回の例ではコンテキストクラスは NORTHWINDEntities クラス、モデルクラスは Products テーブルの CRUD 操作を行うので Products クラスになります。

[Add]ボタンをクリックすると Controller と View が自動生成されプロジェクトに追加されます。


以上の操作は Visual Studio 2010 で MVC4 アプリを作る場合もほぼ同じですが、生成される EDM, Controller, View はかなり進化しているようです。主な点は以下の通りです。

  1. 生成される EDM は EF6 の DbContext ベース。そのためか、Code First で作ったモデルと DB First で作ったモデルは共存可能になっている。(Visual Studio 2010 ではデフォルトで EF4 の ObjectContext ベース。Code First で作ったモデルとは同じプロジェクト内では共存不可)
  2. 一覧 (Index) 画面には CatrgoryID, SupplierID(数字)ではなく、ナビゲーションプロパティをたどって CategoryName, CompanyName を取得してそれが表示される。さらに、Linq 式に Include メソッドを使いプロパティを先読みするという配慮もされている。
  3. 新規作成 (Create)、編集 (Edit) 画面では CatrgoryID, SupplierID にドロップダウンリストを使用して CategoryName, CompanyName が表示され、ユーザーは数字ではなくそれに該当する名前を見て選択できる。
  4. View には @Html.AntiForgeryToken()、Controller のアクションメソッドには ValidateAntiForgeryToken 属性が自動的に付与される。
  5. オーバーポスティングアタック対策として引数にホワイトリスト(Bind 属性)が自動的に付与される。

Tags: ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar