1. Qiita
  2. 投稿
  3. asp

ASP.NET Core と Classic ASP の Cookie 共有について

  • 3
    いいね
  • 0
    コメント

n 年前に作った既存の Classic ASP (以下 ASP と記載) は動かしつつ、新しい機能は ASP.NET Core で実装していきたい :laughing: というケースを想定して Cookie を共有する検証してみました :raised_hand:

ASP での取得 / 設定

まず ASP (Shift-JISのとき)での Cookie 作成のコードを書いて実行してみます。
ASP では下記の hoge1 のようなサブキーが使えます。
(ちなみにサブキーは ASP.NET WebForm/MVC でも使えます)

SetCookie.asp
<%

Response.Write "Set Cookie <br/>"

Response.Cookies("test1") = ""
Response.Cookies("test1")("hoge1") = "こんにちは"
Response.Cookies("test1")("hoge2") = "こんばんは"
Response.Cookies("test1").domain = "localhost"
Response.Cookies("test1").path = "/"

Response.Write "Done"

%>

test1 という Cookie ができました。

image

hoge2=%82%B1%82%F1%82%CE%82%F1%82%CD&hoge1=%82%B1%82%F1%82%C9%82%BF%82%CD

次は、GetCookie.asp を作って Cookie を取得してみます。

GetCookie.asp
<%

Response.Write "Get Cookie"
Response.Write "<br/>"
Response.Write Request.Cookies("test1")("hoge1")
Response.Write "<br/>"
Response.Write Request.Cookies("test1")("hoge2")
Response.Write "<br/>"
Response.Write "Done"

%>

ブラウザでアクセスするとちゃんと取得できました。

image

ASP.NET Core での取得

ASP.NET Core のテンプレートアプリケーションで Cookie 操作をしてみます。
見ての通り、取得も設定も string の key/value を設定するだけのシンプルなインターフェースになっています。サブキーはデフォルトでは使えません。(2016-12-22時点では

HomeController.cs
public IActionResult About()
{
    // Set Cookie 
    Response.Cookies.Append("test2", "こんにちは", new CookieOptions() {Path = "/"});
    return View();
}

public IActionResult Contact()
{    
    // Get Cookie
    ViewData["test1"] = Request.Cookies["test1"];
    ViewData["test2"] = Request.Cookies["test2"];
    return View();
}
Contact.cshtml
<h3>test1 = @ViewData["test1"]</h3>
<h3>test2 = @ViewData["test2"]</h3>

About ページにアクセスすると Cookie ができます。

image

%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF

Contact ページにアクセスすると、ASP.NET Core で設定した test2 はちゃんと表示されましたが、
ASP 側で設定した test1 はいろいろやらないといけなそうです。

image

test1hoge1 サブキーを取得してみます。
&= で分解するだけですね。

        public IActionResult Contact()
        {
            var test1 = Request.Cookies["test1"]
                                .Split('&')
                                .Select(k => k.Split('='))
                                .ToDictionary(s => s[0]?.Trim(), s => s[1]?.Trim());
            ViewData["test1"] = test1["hoge1"];

            ViewData["test2"] = Request.Cookies["test2"];
            return View();
        }

hoge1 がとりあえず取り出せました。
が、Cookie に持っているのは %82%B1%82%F1%82%C9%82%BF%82%CD なのに、%82%B1%82%F1%82ɂ%BF%82%CD が表示されています。
Cookie を参照すると Uri.UnescapeDataString メソッドで自動的に Unescape されますが、
test1 は Shift-JIS なので、UTFで Unescape されるとまずいですね。

(参考:RequestCookieCollection のソース)

image

そこで、Cookie Value を Unescape しないで Cookie を取得する RawValueCookies 拡張メソッドを作ってみます。

        public IActionResult Contact()
        {
            var test1 = Request.RawValueCookies()["test1"]
                                .Split('&')
                                .Select(k => k.Split('='))
                                .ToDictionary(s => s[0]?.Trim(), s => s[1]?.Trim());
            ViewData["test1"] = test1["hoge1"];

            ViewData["test2"] = Request.Cookies["test2"];
            return View();
        }
Extension,cs
        public static RequestCookieCollection RawValueCookies(this HttpRequest request)
        {
            StringValues current;
            if (!request.Headers.TryGetValue(HeaderNames.Cookie, out current))
            {
                current = string.Empty;
            }
            return Parse(current.ToArray());
        }

        public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
        private static RequestCookieCollection Parse(IList<string> values)
        {
            if (values.Count == 0)
            {
                return Empty;
            }

            IList<CookieHeaderValue> cookies;
            if (CookieHeaderValue.TryParseList(values, out cookies))
            {
                if (cookies.Count == 0)
                {
                    return Empty;
                }

                var store = new Dictionary<string, string>(cookies.Count);
                for (var i = 0; i < cookies.Count; i++)
                {
                    var cookie = cookies[i];
                    var name = Uri.UnescapeDataString(cookie.Name);
                    //value を Uri.UnescapeDataString しない
                    var value = cookie.Value;
                    store[name] = value;
                }

                return new RequestCookieCollection(store);
            }
            return Empty;
        }

Unescape しないで取り出せました!

image

あとは Shift-JIS から UTF-8 に変換すると・・・

        public IActionResult Contact()
        {
            var test1 = Request.RawValueCookies()["test1"]
                                .Split('&')
                                .Select(k => k.Split('='))
                                .ToDictionary(s => s[0]?.Trim(), s => s[1]?.Trim());
            var bytes = Encoding.ASCII.GetBytes(test1["hoge1"]);
            var decode = System.Net.WebUtility.UrlDecodeToBytes(bytes, 0, bytes.Length);
            var convert = Encoding.Convert(Encoding.GetEncoding("Shift-JIS"), Encoding.UTF8, decode);
            ViewData["test1"] = Encoding.UTF8.GetString(convert);

            ViewData["test2"] = Request.Cookies["test2"];
            return View();
        }

こんにちは :smiley:

image

ASP.NET Core での設定

ASP の Cookie を Raw Value で取得したということは設定時は Raw Value で戻してあげないといけないですね。Append メソッドで value をエスケープしているので、ここだけ直した拡張メソッドを用意すればよさそうにみえます。
が、SetCookieHeaderValue 側のチェックに引っかかるので、ダミーを入れてヘッダーにセットする直前に置き換えてあげる必要があります :sweat_smile:

        public IActionResult Contact()
        {
            var test1 = Request.RawValueCookies()["test1"]
                                .Split('&')
                                .Select(k => k.Split('='))
                                .ToDictionary(s => s[0]?.Trim(), s => s[1]?.Trim());
            var bytes = Encoding.ASCII.GetBytes(test1["hoge1"]);
            var decode = System.Net.WebUtility.UrlDecodeToBytes(bytes, 0, bytes.Length);
            var convert = Encoding.Convert(Encoding.GetEncoding("Shift-JIS"), Encoding.UTF8, decode);
            ViewData["test1"] = Encoding.UTF8.GetString(convert);

            // set cookie
            Response.AppendRowValueCookie("test1", string.Join("&", test1.Select(s => s.Key + "=" + s.Value)), new CookieOptions() {Path = "/"});

            ViewData["test2"] = Request.Cookies["test2"];
            return View();
        }
Extension.cs
       public static void AppendRowValueCookie(this HttpResponse response, string key, string value, CookieOptions options)
        {
            const string dummyValueString = "COOKIE_DUMMY_VALUE";
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            // set dummy
            var setCookieHeaderValue = new SetCookieHeaderValue(Uri.EscapeDataString(key), dummyValueString)
            {
                Domain = options.Domain,
                Path = options.Path,
                Expires = options.Expires,
                Secure = options.Secure,
                HttpOnly = options.HttpOnly,
            };

            // replace dummy
            var cookieValue = setCookieHeaderValue.ToString().Replace(dummyValueString, value);
            response.Headers[HeaderNames.SetCookie] = StringValues.Concat(response.Headers[HeaderNames.SetCookie],  cookieValue);
        }

その他の注意事項

.NET Core で Shift-JIS を扱うには System.Text.Encoding.CodePages が必要です。

project.json
"dependencies": {
  "System.Text.Encoding.CodePages": "4.3.0"
}
Startup.cs
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

謝辞

ハマってたら @shibayan がアドバイスくれたので感謝
image