January 25, 2021

Коллекция Cookies в HttpResponse и HttpRequest

Как-то, просматривая вопросы на Stackoverflow, наткнулся на вопрос, связанный с коллекцией кук в запросе и ответе. Вкратце вопрос можно сформулировать так:

Почему после добавления куки "ABC" в Response.Cookies с новым значением в Request.Cookies["ABC"] остается лежать старое значение?

Первая же мысль - это же две различные коллекции , вот и не обновляется! Но не все так просто.

Открываем документацию

ASP.NET includes two intrinsic cookie collections... After you add a cookie by using the HttpResponse.Cookies collection, the cookie is immediately available in the HttpRequest.Cookies collection, even if the response has not been sent to the client.

То есть, платформа следит за тем, чтобы куки были доступны в обеих коллекциях. Почему же мы видим старое значение?

У нас же есть Github, и нужные сырцы есть там! Нужные классы находятся в пространстве System.Web. Свойство Cookies имеет тип HttpCookieCollection

Нас интересует, как происходит добавление куки в коллекцию:

/// <devdoc>
///    <para>
///       Adds a cookie to the collection.
///    </para>
/// </devdoc>
public void Add(HttpCookie cookie) 
{
   if (_response != null)
        _response.BeforeCookieCollectionChange();

    AddCookie(cookie, true);

    if (_response != null)
       _response.OnCookieAdd(cookie);
}

Переходим к методу HttpResponse.OnCookieAdd:

internal void OnCookieAdd(HttpCookie cookie) {
    // add to request's cookies as well
    Request.AddResponseCookie(cookie);
}

где Request - связанный через контекст экземпляр HttpRequest:

internal HttpRequest Request {
    get {
        if (_context == null)
            return null;
        return _context.Request;
    }
}

Посмотрим в HttpRequest.cs

//
// Request cookies sometimes are populated from Response
// Here are helper methods to do that.
//

/*
 * Add response cookie to request collection (can override existing)
 */
internal void AddResponseCookie(HttpCookie cookie) {
 // cookies collection

 if (_cookies != null)
  _cookies.AddCookie(cookie, true);

 // cookies also go to parameters collection

 if (_params != null) {
  _params.MakeReadWrite();
  _params.Add(cookie.Name, cookie.Value);
  _params.MakeReadOnly();
 }
}

Вот за счет такой цепочки вызовов происходит пополнение коллекции HttpRequest.

Так почему же у нас нет нового значения куки? На самом деле оно есть и доступно, например, по числовому индексу.

Вернемся к классу HttpCookieCollection.cs. При добавлении новой куки происходит вызов метода BaseAdd(cookie.Name, cookie) базового класса

/// <devdoc>
///    <para>Adds an entry with the specified key and value into the 
///    <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
/// </devdoc>
protected void BaseAdd(String name, Object value) {
 if (_readOnly)
  throw new NotSupportedException(SR.GetString(SR.CollectionReadOnly));

 NameObjectEntry entry = new NameObjectEntry(name, value);

 // insert entry into hashtable
 if (name != null) {
  if (_entriesTable[name] == null)
   _entriesTable.Add(name, entry);
 }
 else { // null key -- special case -- hashtable doesn't like null keys
  if (_nullKeyEntry == null)
   _nullKeyEntry = entry;
 }

 // add entry to the list
 _entriesArray.Add(entry);

 _version++;
}

Как мы видим есть два поля: Hashtable _entriesTable и ArrayList _entriesArray. Первое поле используется для поиска по имени, второе для поиска по индексу. Значимое отличие между двумя коллекциями заключается в том, что в первую коллекцию элемент с дублирующим именем не попадает.

В нашем случае возникает именно такая ситуация - новая кука попадает только во вторую коллекцию и доступна по индексу, но не доступна по имени.

Если ваш код заполняет HttpResponse.Cookies, а потом что-то проверяет в коллекции HttpRequest.Cookies по имени - убедитесь, что вы понимаете как это работает и при необходимости сперва удаляйте ненужные куки из реквеста.