A lot of web applications use cookies for enabling special features. For my company (Exact) for example, we use a cookie to store the latest division (code) that was selected by a user. This is not modelled DDD right now. How should it look like if it did? How to tackle the fact that an HTTP cookie is sealed, as we don’t want to introduce some (static) tooling or helper class?
First of all, I created an abstract wrapped cookie class. This allows developers to add extra (factory) methods, constructors, and properties based on the domain where the specific cookie has to be used. A cookie is a quiet generic thing, and we want to model domain specific implementations. Note that, as a result of that, the Value and Values property of the HTTP cookie are not publicly exposed.
In this case, the base class has besides the required wrapping a constructor with a user ID (GUID) and a static method to create a cookie name, as that is the default for our company. That is not necessarily something everybody should need.
For the specific cookie we have one public constructor, that creates an instance based on the user ID and its division code (a custom value object, containing that logic). Because, in the end, that is what this cookie is all about in our domain.
In this case it is extremely important that is always possible to (implicitly) cast between the wrapped and the original cookie. That makes the usage if the class way more easy.
[code=c#]using System;
using System.Web;
namespace HelloWorld.Web
{
///
public class DivisionCodeCookie : WrappedCookie
{
///
private const string DivisionCodeKey = “Division”;
///
///
The user ID.
///
The division code.
public DivisionCodeCookie(Guid userId, DivisionCode code)
: base(userId)
{
this.Code = code;
}
///
private DivisionCodeCookie(HttpCookie cookie) : base(cookie) { }
///
public DivisionCode Code
{
get { return DivisionCode.TryParse(UnderlingCookie.Values[DivisionCodeKey]); }
set { UnderlingCookie.Values[DivisionCodeKey] = value.ToString(); }
}
///
public DivisionCodeCookie Copy() { return new DivisionCodeCookie(this.UserId, this.Code); }
///
///
/// Making the cast implicit allows the use of wrapped cookie when a HTTP cookie is asked.
///
public static implicit operator DivisionCodeCookie(HttpCookie http) { return new DivisionCodeCookie(http); }
}
}[/code]
The base class.
[code=c#]using System;
using System.Diagnostics;
using System.Web;
namespace HelloWorld.Web
{
///
///
/// It is a wrapper that allows to add custom logic to the cookie.
///
[DebuggerDisplay("{DebuggerDisplay}")]
public abstract class WrappedCookie
{
///
protected WrappedCookie(HttpCookie httpCookie)
{
if (httpCookie == null) { throw new ArgumentNullException(“httpCookie”); }
this.UnderlingCookie = httpCookie;
}
///
protected WrappedCookie(Guid userId) : this(GetCookieName(userId)) { }
///
protected WrappedCookie(string name): this(new HttpCookie(name)){}
///
protected HttpCookie UnderlingCookie { get; set; }
///
public Guid UserId
{
get
{
Guid userid;
if (this.Name.StartsWith(“ExactServer{“)&& Guid.TryParseExact(this.Name.Substring(11), “B”, out userid))
{
return userid;
}
return Guid.Empty;
}
set { this.Name = GetCookieName(value); }
}
///
public string Name
{
get { return UnderlingCookie.Name; }
set{ UnderlingCookie.Name = value;}
}
///
public string Domain
{
get { return UnderlingCookie.Domain; }
set { UnderlingCookie.Domain = value; }
}
///
public string Path
{
get { return UnderlingCookie.Path; }
set { UnderlingCookie.Path = value; }
}
///
public DateTime Expires
{
get { return UnderlingCookie.Expires; }
set { UnderlingCookie.Expires = value; }
}
///
public bool HttpOnly
{
get { return UnderlingCookie.HttpOnly; }
set { UnderlingCookie.HttpOnly = value; }
}
///
public bool Secure
{
get { return UnderlingCookie.Secure; }
set { UnderlingCookie.Secure = value; }
}
///
public bool Shareable
{
get { return UnderlingCookie.Shareable; }
set { UnderlingCookie.Shareable = value; }
}
///
///
/// Making the cast implicit allows the use of wrapped cookie when an HTTP cookie is asked.
///
public static implicit operator HttpCookie(WrappedCookie wrapped) { return wrapped.UnderlingCookie; }
///
public void Cleanup()
{
UnderlingCookie.Expires = DateTime.Now.AddMinutes(-1);
UnderlingCookie.Values.Clear();
}
///
public static string GetCookieName(Guid userId)
{
return string.Format(“ExactServer{0:B}”, userId);
}
///
protected virtual string DebuggerDisplay { get { return string.Format(“Cookie[{0}], Value: {1}, Expires: {2:yyyy-MM-dd HH:mm}”, this.Name, this.UnderlingCookie.Value, this.Expires); } }
}
}[/code]