C# 我如何复制CookieContainer?

C# 我如何复制CookieContainer?,c#,.net,cookies,C#,.net,Cookies,给出了CookieContainer的实例 此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。任何实例成员都不能保证线程安全 因此,如果没有同步,我无法跨多个并发HTTP请求使用相同的容器。不幸的是,从MSDN的文档来看,还不清楚如何正确地同步它 解决方案是为每个请求使用主容器的副本,一旦请求完成,副本中的cookie可以合并回主容器。可以以同步方式创建副本和合并 因此,问题是:如何复制CookieContainer类的实例?您可以使用反射获取与所有Uris相关的所有c

给出了CookieContainer的实例

此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。任何实例成员都不能保证线程安全

因此,如果没有同步,我无法跨多个并发HTTP请求使用相同的容器。不幸的是,从MSDN的文档来看,还不清楚如何正确地同步它

解决方案是为每个请求使用主容器的副本,一旦请求完成,副本中的cookie可以合并回主容器。可以以同步方式创建副本和合并


因此,问题是:如何复制CookieContainer类的实例?

您可以使用反射获取与所有
Uri
s相关的所有cookie,然后创建新的
CookieContainer
并将其添加到其中,可能如下所示:

public static CookieContainer DeepClone(CookieContainer src)
{
    CookieContainer cookieContainer = new CookieContainer();

    Hashtable table = (Hashtable)src.GetType().InvokeMember("m_domainTable", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, src, new object[] { });

    foreach (var tableKey in table.Keys)
    {
        String str_tableKey = (string)tableKey;

        if (str_tableKey[0] == '.')
            str_tableKey = str_tableKey.Substring(1);

        SortedList list = (SortedList)table[tableKey].GetType().InvokeMember("m_list", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, table[tableKey], new object[] { });

        foreach (var listKey in list.Keys)
        {
            String url = "https://" + str_tableKey + (string)listKey;

            CookieCollection collection = src.GetCookies(new Uri(url));

            foreach (Cookie c in collection)
                cookieContainer.Add(new Cookie(c.Name, c.Value, c.Path, c.Domain)
                {
                    Comment = c.Comment,
                    CommentUri = c.CommentUri,
                    Discard = c.Discard,
                    Expired = c.Expired,
                    Expires = c.Expires,
                    HttpOnly = c.HttpOnly,
                    Port = c.Port,
                    Secure = c.Secure,
                    Version = c.Version
                });
        }
    }
    return cookieContainer;
}

CookieContainer类是可序列化的。既然您说过无论如何都需要序列化它,为什么不直接使用BinaryFormatter将其序列化到MemorySteam,然后反序列化以生成副本呢

我知道这太简单了,所以如果没有帮助,请忽略

private CookieContainer CopyContainer(CookieContainer container)
{
    using(MemoryStream stream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, container);
        stream.Seek(0, SeekOrigin.Begin);
        return (CookieContainer)formatter.Deserialize(stream);
    }
}

你可以用反射来做。这可能会得到改进,YMMV:

//Set up the source cookie container
var cookieContainerA = new CookieContainer();
cookieContainerA.Add(new Uri("http://foobar.com"), new Cookie("foo", "bar"));
cookieContainerA.Add(new Uri("http://foobar.com"), new Cookie("baz", "qux"));
cookieContainerA.Add(new Uri("http://abc123.com"), new Cookie("abc", "123"));
cookieContainerA.Add(new Uri("http://abc123.com"), new Cookie("def", "456"));

//Set up our destination cookie container
var cookieContainerB = new CookieContainer();

//Get the domain table member
var type = typeof(CookieContainer);
var domainTableField = type.GetField("m_domainTable", BindingFlags.NonPublic | BindingFlags.Instance);
var domainTable = (Hashtable)domainTableField.GetValue(cookieContainerA);

//Iterate the domain table
foreach (var obj in domainTable)
{
  var entry = (DictionaryEntry)obj;

  //The domain is the key (we only need this for our Console.WriteLine later)
  var domain = entry.Key;
  var valuesProperty = entry.Value.GetType().GetProperty("Values");
  var values = (IList)valuesProperty.GetValue(entry.Value);

  foreach (var valueObj in values)
  {
    //valueObj is a CookieCollection, cast and add to our destination container
    var cookieCollection = (CookieCollection)valueObj;
    cookieContainerB.Add(cookieCollection);

    //This is a dump of our source cookie container
    foreach (var cookieObj in cookieCollection)
    {
      var cookie = (Cookie)cookieObj;
      Console.WriteLine("Domain={0}, Name={1}, Value={2}", domain, cookie.Name, cookie.Value);
    }
  }
}


//Test the copying
//var foobarCookies = cookieContainerB.GetCookies(new Uri("http://foobar.com"));
//var abc123Cookies = cookieContainerB.GetCookies(new Uri("http://abc123.com"));
看看这个类,您会发现当cookie集合中发生更改时,并发场景会发生,对吗

您会注意到,CookieContainer的作者在这些集合中使用了
lock{}
SyncRoot
来更改代码的各个部分,我不认为这种方法不适用于并发场景

此外,您还可以注意到,任何添加的内容都是字面上的克隆,因此容器内的cookie和所做的所有操作都不会与cookie容器外的对象引用相混淆。在最糟糕的情况下,我丢失了一些东西,克隆也给了我们一个提示,您必须复制什么,以及如何做到这一点,在使用其他帖子中描述的反射方法的情况下(我个人不认为它是黑客,因为它符合要求,它是管理的、合法的和安全的代码:))。
事实上,MSDN文档中提到的都是“任何实例成员都不能保证线程安全。”-这是一种提醒,因为你是对的,你真的需要小心。然后使用这样的语句,您可以假设基本上有两件事:1)非静态成员根本不安全。2) 有些成员可以是线程安全的,但它们没有正确的文档记录。

只是添加了我自己的twist,以转换为Base64字符串

public static string ToBase64(CookieContainer container)
{
    string str = null;

    byte[] bytes = null;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, container);
        bytes = ms.ToArray();
    }
    str = Convert.ToBase64String(bytes);

    return str;
}

public static CookieContainer FromBase64(string container_base64)
{
    CookieContainer cc = null;

    byte[] bytes = Convert.FromBase64String(container_base64);
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        BinaryFormatter bf = new BinaryFormatter();
        cc = (CookieContainer)bf.Deserialize(ms);
    }

    return cc;
}

你看过使用Reflector或DotPeak的源代码吗?我相信CookieContainer是线程安全的,我已经多次使用它来处理共享单个CookieContainer的许多web服务请求,我从来没有遇到过任何问题。我做到了,我仍然需要能够复制它以进行序列化。我认为这符合您的要求。对于同步而言,
lock
有什么问题?@AmerSawan,这是一个黑客攻击,不是吗?@AmerSawan由于内存流没有保存非托管资源,因此没有必要在那里进行dispose/close操作。此外,我假设我的代码足以证明这一原理,代码的任何用户都会足够聪明,能够适应自己的使用,并根据需要调用Dispose/Close。因为
MemoryStream
实现了
IDisposable
,您需要调用
Dispose
,否则FXCop将报告代码警告。@bonomo是的,它看起来对我有效,但web开发不是我的主要工作,您可能希望明天尝试,并让我知道它是否对您有效。我主要为嵌入式系统和桌面应用程序编写软件SVS2019 v16.5.4,带有.NET 4.7.2的web应用程序,Win 2012R2标准x64-尝试调用formatter.Serialize()会导致异常“对象图不能为空。\r\n参数名称:图”。我做错了什么?使用反射是一种技巧。如果你看一下内部,还有5个字段你没有提到,它们定义了容器的状态。你也要抄吗?如果在CookieContainer类之外还有什么东西需要复制的话?这是一种黑客行为,但不管出于什么原因,你试图做的事情并不简单,所以黑客就是你剩下的。如果你反对反思,也许你可以提一下。其中3个内部构件映射到属性(Capacity、MaxCookieSize和PerDomainCapacity),您可以通过分配来复制这些属性。计数是N/A,这会留下m_fqdnMyDomain,我不确定TBHAlan的序列化解决方案是否是最好的选择,因为你可以重新发布sombody的else答案:使用反射是一种攻击。如果你看一下内部,还有5个字段你没有提到,它们定义了容器的状态。你也要抄吗?如果CookieContainer类之外还有什么东西需要复制的话?反射和序列化都是黑客攻击,但我认为使用反射更常见,性能更友好!序列化是合法的(经批准的)黑客行为。