n 年前に作った既存の Classic ASP (以下 ASP と記載) は動かしつつ、新しい機能は ASP.NET Core で実装していきたい というケースを想定して Cookie を共有する検証してみました
ASP での取得 / 設定
まず ASP (Shift-JISのとき)での Cookie 作成のコードを書いて実行してみます。
ASP では下記の hoge1
のようなサブキーが使えます。
(ちなみにサブキーは ASP.NET WebForm/MVC でも使えます)
<%
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 ができました。
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 を取得してみます。
<%
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"
%>
ブラウザでアクセスするとちゃんと取得できました。
ASP.NET Core での取得
ASP.NET Core のテンプレートアプリケーションで Cookie 操作をしてみます。
見ての通り、取得も設定も string の key/value を設定するだけのシンプルなインターフェースになっています。サブキーはデフォルトでは使えません。(2016-12-22時点では)
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();
}
<h3>test1 = @ViewData["test1"]</h3>
<h3>test2 = @ViewData["test2"]</h3>
About ページにアクセスすると Cookie ができます。
%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF
Contact ページにアクセスすると、ASP.NET Core で設定した test2
はちゃんと表示されましたが、
ASP 側で設定した test1
はいろいろやらないといけなそうです。
test1
の hoge1
サブキーを取得してみます。
&
と =
で分解するだけですね。
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 のソース)
そこで、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();
}
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 しないで取り出せました!
あとは 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();
}
こんにちは
ASP.NET Core での設定
ASP の Cookie を Raw Value で取得したということは設定時は Raw Value で戻してあげないといけないですね。Append メソッドで value をエスケープしているので、ここだけ直した拡張メソッドを用意すればよさそうにみえます。
が、SetCookieHeaderValue
側のチェックに引っかかるので、ダミーを入れてヘッダーにセットする直前に置き換えてあげる必要があります
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();
}
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
が必要です。
"dependencies": {
"System.Text.Encoding.CodePages": "4.3.0"
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
謝辞
ハマってたら @shibayan がアドバイスくれたので感謝