Question

I am having issues with my cookie management code. On every page I look to see if a key value has been set. My problem is that looking at the cookie creates an empty cookie.

Locally, I can look at the cookie and then set the key value and all is right with the world. But when I move my code to the test server, everything behaves differently. For one, locally, only one cookie is issued (shown in chrome's tools). On the server, 2 cookies are issued with the same code.

I wrote this code about 4 years ago, and it was designed for .net 2.0 when the HttpCookie behavior was changed so that looking at the cookie created an empty one if it did not exist. Previously, in .Net 1.1 null was returned. Now I'm wondering something fundamental was changed between 2.0 and 4.0.

I should note that my local machine is Windows 7 and the server is a Windows 2003 server - not that it should make any difference. The only thing to note is that the app is running in a virtual directory on the server. Could that play a part in the issues? I'm not sure. I tried setting the path of the cookie to the path of the virtual directory with no luck.

My cookie has several layers, including a base layer for dealing directly with the http cookie, an encryption layer that can be turned on or off for debugging, and then a management layer. I'm going to share my code in the hopes I have a glaring error in my logic.

I also want to mention that I am using the FormsAuthentication cookie for something else. This cookie is for some extraneous, but required, data.

My base cookie layer:

public abstract class BaseCookie
{
    #region Private Variables
    private string cookieName;
    private int timeout = 30;
    private ExpirationMode expirationMode = ExpirationMode.SlidingExpiration;
    private string domain;
    private bool httpOnly = true;
    private string path = "/";
    private bool secure;
    private string cookieValue;
    #endregion

    #region Public Properties

    /// <summary>
    /// The name of the cookie as it appears in the request and response
    /// </summary>
    protected string CookieName
    {
        get { return cookieName; }
        set { cookieName = value; }
    }

    /// <summary>
    /// The expiration mode of the cookie (default SlidingExpiration)
    /// </summary>
    public ExpirationMode ExpirationMode
    {
        get { return expirationMode; }
    }

    /// <summary>
    /// The timeout in minutes (default 30)
    /// </summary>
    public int Timeout
    {
        get { return timeout; }
        set { timeout = value; }
    }

    /// <summary>
    /// The cookie domain (default String.Empty)
    /// </summary>
    public string Domain
    {
        get { return domain; }
        set { domain = value; }
    }

    /// <summary>
    /// Whether or not the cookie is http only (default true)
    /// </summary>
    public bool HttpOnly
    {
        get { return httpOnly; }
        set { httpOnly = value; }
    }

    /// <summary>
    /// The path of the cookie (default "/")
    /// </summary>
    public string Path
    {
        get { return path; }
        set { path = value; }
    }

    /// <summary>
    /// Whether or not the cookie supports https (default false)
    /// </summary>
    public bool Secure
    {
        get { return secure; }
        set { secure = value; }
    }

    /// <summary>
    /// The Value of the cookie (NOTE: this should only be used for setting the value when calling AppendNewCookie().
    /// This will not change the value of the cookie after initialization.  Use SetCookieValue and GetCookieValue after initialization.
    /// </summary>
    public string Value
    {
        get { return cookieValue; }
        set { cookieValue = value; }
    }

    /// <summary>
    /// The cookie in the Request
    /// </summary>
    private HttpCookie RequestCookie
    {
        get
        {
            return HttpContext.Current.Request.Cookies.Get(CookieName);
        }
    }

    /// <summary>
    /// The cookie in the Response
    /// </summary>
    private HttpCookie ResponseCookie
    {
        get
        {
            return HttpContext.Current.Response.Cookies.Get(CookieName);
        }
    }

    #endregion

    #region Constructors
    /// <summary>
    /// Constructor setting the name of the cookie with Sliding Expiration
    /// </summary>
    /// <param name="cookieName">the name of the cookie</param>
    public BaseCookie(string cookieName)
        : this(cookieName, ExpirationMode.SlidingExpiration)
    {

    }

    /// <summary>
    /// Constructor setting the name of the cookie and the expiration mode 
    /// </summary>
    /// <param name="cookieName">the name of the cookie</param>
    /// <param name="expirationMode">the Olympus.Cookies.ExpirationMode of the cookie</param>
    public BaseCookie(string cookieName, ExpirationMode expirationMode)
    {
        this.cookieName = cookieName;

        CookieConfig config = Configuration.CookieConfiguration.Cookies[cookieName];

        if (config != null)
        {
            Domain = config.Domain;
            HttpOnly = config.HttpOnly;
            Path = config.Path;
            Secure = config.Secure;
            Timeout = config.Timeout;
        }

        //EnsureCookie();
    }
    #endregion

    /// <summary>
    /// This method ensures that the cookie is not empty if it exists in either the request or response.
    /// Due to changes in the cookie model for 2.0, this step is VITAL to cookie management.
    /// </summary>
    protected void EnsureCookie()
    {
        //if the cookie doesn't exist in the response (hasn't been altered or set yet this postback)
        if (IsNull(ResponseCookie))
        {
            //if the cookie exists in the request
            if (!IsNull(RequestCookie))
            {
                //get the cookie from the request
                HttpCookie cookie = RequestCookie;

                //update the expiration
                if (ExpirationMode == ExpirationMode.NoExpiration)
                {
                    cookie.Expires = DateTime.Now.AddYears(10);
                }
                else
                {
                    cookie.Expires = DateTime.Now.AddMinutes(Timeout);
                }

                //set the cookie into the response
                HttpContext.Current.Response.Cookies.Set(cookie);
            }
            else
            {
                //if the response and request cookies are null, append a new cookie
                AppendNewCookie();
            }
        }
    }

    /// <summary>
    /// Append an empty cookie to the Response
    /// </summary>
    public virtual void AppendNewCookie()
    {
        HttpCookie cookie = new HttpCookie(CookieName);

        cookie.Domain = Domain;
        cookie.HttpOnly = HttpOnly;
        cookie.Path = Path;
        cookie.Secure = Secure;

        cookie.Value = Value;

        if (ExpirationMode == ExpirationMode.NoExpiration)
        {
            cookie.Expires = DateTime.Now.AddYears(10);
        }
        else
        {
            cookie.Expires = DateTime.Now.AddMinutes(Timeout);
        }

        HttpContext.Current.Response.Cookies.Add(cookie);
    }

    /// <summary>
    /// Determine if the Cookie is null.
    /// </summary>
    /// <remarks>
    /// Due to changes in the 2.0 cookie model, looking in the request or respnse creates an empty cookie with an 
    /// expiration date of DateTime.MinValue.  Previously, a null value was returned.  This code calls one of these 
    /// empty cookies a null cookie.
    /// </remarks>
    /// <param name="cookie">the cookie to test</param>
    /// <returns>System.Boolean true if the cookie is "null"</returns>
    protected static bool IsNull(HttpCookie cookie)
    {
        if (cookie == null)
        {
            return true;
        }
        else
        {
            if (String.IsNullOrEmpty(cookie.Value) && cookie.Expires == DateTime.MinValue)
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// Update the expiration of the response cookie
    /// <remarks>
    /// If this is not done, the cookie will never expire because it will be updated with an expiry of DateTime.Min
    /// </remarks>
    /// </summary>
    protected void SetExpiration()
    {
        if (ExpirationMode == ExpirationMode.NoExpiration)
        {
            ResponseCookie.Expires = DateTime.Now.AddYears(10);
        }
        else
        {
            ResponseCookie.Expires = DateTime.Now.AddMinutes(Timeout);
        }
    }

    /// <summary>
    /// Set the value of a cookie.
    /// </summary>
    /// <remarks>
    /// Setting value will override all attributes.
    /// </remarks>
    /// <param name="value">the value to set the cookie</param>
    public virtual void SetCookieValue(string value)
    {
        //EnsureCookie();

        ResponseCookie.Value = value;

        SetExpiration();
    }

    /// <summary>
    /// Get the default value (the first value) of the cookie
    /// </summary>
    /// <remarks>
    /// Getting the value only returns the first value (values[0]) because it has no key.
    /// </remarks>
    /// <returns>System.String</returns>
    public virtual string GetCookieValue()
    {
        //EnsureCookie();

        string returnValue = "";

        if (RequestCookie.Values.Count > 0)
        {
            returnValue = RequestCookie.Values[0];
        }

        return returnValue;
    }

    /// <summary>
    /// Set a key/value pair
    /// </summary>
    /// <param name="key">the key for the pair</param>
    /// <param name="value">the value to assign to the key</param>
    public virtual void SetKeyValue(string key, string value)
    {
        //EnsureCookie();

        ResponseCookie.Values.Set(key, value);

        SetExpiration();
    }

    /// <summary>
    /// Get a value for a given key
    /// </summary>
    /// <param name="key">the key to find a value of</param>
    /// <returns>System.String</returns>
    public virtual string GetKeyValue(string key)
    {
        //EnsureCookie();

        return RequestCookie.Values[key];
    }

    /// <summary>
    /// Expire the cookie
    /// </summary>
    public virtual void Expire()
    {
        //Setting the expiration does not remove the cookie from the next request
        ResponseCookie.Expires = DateTime.Now.AddDays(-1);
    }
}

My encryption layer (batteries not included)

internal sealed class EncryptedCookie : BaseCookie
{
    private string decryptedCookieName;

    new public string Value
    {
        get
        {
            if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
            {
                return Encryption.DecryptQueryString(base.Value);
            }
            else
            {
                return base.Value;
            }
        }
        set
        {
            if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
            {
                base.Value = Encryption.EncryptQueryString(value);
            }
            else
            {
                base.Value = value;
            }
        }
    }

    public EncryptedCookie(string cookieName)
        : base(cookieName)
    {
        decryptedCookieName = cookieName;

        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            CookieName = Encryption.EncryptQueryString(cookieName);
        }

        EnsureCookie();
    }

    public EncryptedCookie(string cookieName, ExpirationMode expirationMode)
        : base(cookieName, expirationMode)
    {
        decryptedCookieName = cookieName;

        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            CookieName = Encryption.EncryptQueryString(cookieName);
        }

        EnsureCookie();
    }

    public override string GetCookieValue()
    {
        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            return Encryption.DecryptQueryString(base.GetCookieValue());
        }
        else
        {
            return base.GetCookieValue();
        }
    }

    public override void SetCookieValue(string value)
    {
        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            base.SetCookieValue(Encryption.EncryptQueryString(value));
        }
        else
        {
            base.SetCookieValue(value);
        }
    }

    public override void SetKeyValue(string key, string value)
    {
        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            base.SetKeyValue(Encryption.EncryptQueryString(key), Encryption.EncryptQueryString(value));
        }
        else
        {
            base.SetKeyValue(key, value);
        }
    }

    public override string GetKeyValue(string key)
    {
        if (Configuration.CookieConfiguration.Cookies[decryptedCookieName].EnableEncryption)
        {
            return Encryption.DecryptQueryString(base.GetKeyValue(Encryption.EncryptQueryString(key)));
        }
        else
        {
            return base.GetKeyValue(key);
        }
    }
}

And finally, the cookie management layer that wraps it all up.

public sealed class Cookie
{
    string cookieName;

    public Cookie(string cookieName)
    {
        this.cookieName = cookieName;
    }

    public void AppendCookie()
    {
        EncryptedCookie cookie = new EncryptedCookie(cookieName);

        cookie.AppendNewCookie();
    }

    public void SetAttribute(string attributeName, string attributeValue)
    {
        EncryptedCookie cookie = new EncryptedCookie(cookieName);

        cookie.SetKeyValue(attributeName, attributeValue);
    }

    public void SetValue(string value)
    {
        EncryptedCookie cookie = new EncryptedCookie(cookieName);

        cookie.SetCookieValue(value);
    }

    public string GetValue()
    {
        EncryptedCookie cookie = new EncryptedCookie(cookieName);

        return cookie.GetCookieValue();
    }

    public string GetAttribute(string attributeName)
    {
        EncryptedCookie cookie = new EncryptedCookie(cookieName);

        return cookie.GetKeyValue(attributeName);
    }
}

There is a custom configuration along with this that helps configure the cookie(s). Here's what that looks like:

    <cookie>
        <cookies>
            <!-- enableEncryption: [ true | false ] -->
            <!-- expirationMode: [ SlidingExpiration | NoExpiration ] -->
            <!-- timeout: the timeout in minutes (Default 30)-->
            <!-- httpOnly: [ true | false ] -->
            <!-- secure: [ true | false ] -->
            <add name="MyCookie" enableEncryption="true" expirationMode="SlidingExpiration" timeout="64800" domain="" httpOnly="true" path="" secure="false" />
        </cookies>
    </cookie>

Now, my code looks something like this within my web app:

    public int MyId
    {
        get
        {

            Cookie cookie = new Cookie(ConfigurationHelper.Cookies.MyCookie);

            int myId= 0;
            try
            {
                Int32.TryParse(cookie.GetAttribute(BILLERID_KEY), out myId);
            }
            catch (System.NullReferenceException)
            {
                //do nothing on purpose.
            }

            return myId;

        }
        set
        {
            Cookie cookie = new Cookie(ConfigurationHelper.Cookies.MyCookie);

            cookie.SetAttribute(MY_KEY, value.ToString());
        }
    }

    public void SetMyId(int myId)
    {
        Cookie cookie = new Cookie(ConfigurationHelper.Cookies.MyCookie);

        cookie.SetAttribute(MYID_KEY, myId.ToString());

        cookie.AppendCookie();
    }

I call the property first to see if the user's ID has been set. This call is done on every page. Then on another page the user can authenticate and I set the ID into the cookie. It all works locally, but if I hit the homepage on the server (where id does the lookup on the id) and then authenticate, the cookie value is not set. It remains empty.

Was it helpful?

Solution

Is the problem that you are not calling

cookie.AppendCookie();

in the set accessor for MyId? You are calling it in the SetMyId method.

OTHER TIPS

The cookie gets created when you check for it on HttpContext.Current.Response. I worked around this by sticking a copy of the cookie in HttpContext.Current.Items when I set it. Then when I need to check if it's been set or not, I check Items instead of Response.Cookies.

Not the most elegant solution, but it gets the job done.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top