.NETのCookieContainerをシリアライズする
.NETのCookieContainerってなんでSerializeできないんですかね?
HttpWebRequestでcookieを横取りしたり、そもそもそんなことしたいと思うのが筋悪なのか? と、思いつつ幾数年...。
8.1のWindows Universal Appを作っていたら、なんとHttpCookieManagerなどと言うモノが新設されており、こりゃ楽になる! ...と思ったのもつかの間、今作ってるアプリはHTTP関連が全部PCLに入っていたという。
そんなわけで、今更感もありますが、あらためてCookieContainerとは何者かを見直して、この値をDataContractSerializerでシリアライズして保存可能なCookieContainrラッパークラスを実装してみました。
とりあえずDataContractSerializerを使っていますが、XmlSerializerやSerializable用にも書き換えられると思います。
今の所ちゃんと動いているようですが、何かおかしなところがあればツッコミお願いします。
[DataContract] public class DataContractCookieContainer { private DataContractCookieContainer() { this.CookieContainer = new CookieContainer(); } public DataContractCookieContainer(string targetDomain, CookieContainer cookieContainer) { this.CookieContainer = cookieContainer; this.Domain = targetDomain; } // Order is very important factor in deserializing order [DataMember(Order=0)] public string Domain { get; set; } [IgnoreDataMember] public CookieContainer CookieContainer { get; set; } [DataMember(Order=1)] private List<DataContractCookie> Cookies { get { if (string.IsNullOrWhiteSpace(Domain) || !Domain.StartsWith("http")) throw new InvalidOperationException("Domain must be set Uri initializable URL start with http..."); var cookielist = new List<DataContractCookie>(); var collection = this.CookieContainer.GetCookies(new Uri(Domain)); foreach (Cookie c in collection) { cookielist.Add(new DataContractCookie(c)); } return cookielist; } set { this.CookieContainer = new CookieContainer(); if (value != null && !string.IsNullOrWhiteSpace(Domain)) { var collection = new CookieCollection(); foreach (var cookie in value) { collection.Add(cookie.ToCookie()); } this.CookieContainer.Add(new Uri(Domain), collection); } } } [DataContract] private sealed class DataContractCookie { // Error message when you serialize a class by using the XMLSerializer class: "System.InvalidOperationException" // http://support.microsoft.com/kb/330592/en // http://support.microsoft.com/kb/330592/ja (Japanese) private DataContractCookie() { } public DataContractCookie(Cookie cookie) { Comment = cookie.Comment; CommentUri = cookie.CommentUri; Discard = cookie.Discard; Domain = cookie.Domain; Expired = cookie.Expired; Expires = cookie.Expires; HttpOnly = cookie.HttpOnly; Name = cookie.Name; Path = cookie.Path; Port = cookie.Port; Secure = cookie.Secure; Value = cookie.Value; Version = cookie.Version; } public Cookie ToCookie() { var cookie = new Cookie() { Name = Name, Value = Value, Path = Path, Domain = Domain, Comment = Comment, CommentUri = CommentUri, Discard = Discard, Expired = Expired, Expires = Expires, HttpOnly = HttpOnly, Port = Port, Secure = Secure, Version = Version, }; return cookie; } [DataMember] public string Comment { get; set; } [DataMember] public string CommentUrl { get; set; } // Uri == null cause exception [IgnoreDataMember] public Uri CommentUri { get { return string.IsNullOrWhiteSpace(CommentUrl) ? null : new Uri(CommentUrl); } set { CommentUrl = (value == null) ? "" : value.ToString(); } } [DataMember] public bool Discard { get; set; } [DataMember] public string Domain { get; set; } [DataMember] public bool Expired { get; set; } [DataMember] public DateTime Expires { get; set; } [DataMember] public bool HttpOnly { get; set; } [DataMember] public string Name { get; set; } [DataMember] public string Path { get; set; } [DataMember] public string Port { get; set; } [DataMember] public bool Secure { get; set; } [DataMember] public string Value { get; set; } [DataMember] public int Version { get; set; } } }
使い方は、↓こんな感じ。
DataContractCookieContainerはアプリで使用するcookieのドメインのURL(http〜)を指定して初期化してください。クッキーを列挙するのにCookieContainer.GetCookies(uri)を使うしか無いもので。(緩募: Uriで全ドメインを指定する方法)
[DataContract] public class SampleSettings { private SampleSettings() { } public SampleSettings(string targetDomain, CookieContainer cookieContainer = null) { CookieData = new DataContractCookieContainer(targetDomain, cookieContainer ?? new CookieContainer()); } public string ToXml() { try { var serializer = new DataContractSerializer(typeof(SampleSettings)); using (var ms = new MemoryStream()) { serializer.WriteObject(ms, this); return Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length); } } catch (Exception ex) { Debug.Assert(false, "Something Error Handling"); return string.Empty; } } public static SampleSettings ParseXml(string xml) { if (string.IsNullOrWhiteSpace(xml)) return null; var bytes = Encoding.UTF8.GetBytes(xml); try { var serializer = new DataContractSerializer(typeof(SampleSettings)); using (var stm = new MemoryStream(bytes)) { return serializer.ReadObject(stm) as SampleSettings; } } catch (Exception ex) { Debug.Assert(false,"Something Error Handling"); return new SampleSettings(); } } [DataMember] public string Username { get; set; } [DataMember] public int UserID { get; set; } [DataMember] private DataContractCookieContainer CookieData { get; set; } [IgnoreDataMember] public CookieContainer Cookie { get { return CookieData.CookieContainer; } set { CookieData.CookieContainer = value; } } }
順に書いて行ってわかったのは、どうもCookieContainerがシリアライズできないのは、最終的に各cookieが格納されているCookieクラスのCommentUriメンバーにnullが入ってるのが原因っぽいですね。そんなことかよ、という...。もしかしたらもっと深遠な理由があるのかもですが。