Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした