A Simple Entity Tagger
- by Elton Stoneman
In the REST world, ETags are your gateway to performance boosts by letting clients cache responses. In the non-REST world, you may also want to add an ETag to an entity definition inside a traditional service contract – think of a scenario where a consumer persists its own representation of your entity, and wants to keep it in sync. Rather than load every entity by ID and check for changes, the consumer can send in a set of linked IDs and ETags, and you can return only the entities where the current ETag is different from the consumer’s version. If your entity is a projection from various sources, you may not have a persistent ETag, so you need an efficient way to generate an ETag which is deterministic, so an entity with the same state always generates the same ETag. I have an implementation for a generic ETag generator on GitHub here: EntityTagger code sample. The essence is simple - we get the entity, serialize it and build a hash from the serialized value. Any changes to either the state or the structure of the entity will result in a different hash. To use it, just call SetETag, passing your populated object and a Func<> which acts as an accessor to the ETag property: EntityTagger.SetETag(user, x => x.ETag);
The implementation is all in at 80 lines of code, which is all pretty straightforward:
var eTagProperty = AsPropertyInfo(eTagPropertyAccessor);
var originalETag = eTagProperty.GetValue(entity, null);
try
{
ResetETag(entity, eTagPropertyAccessor);
string json;
var serializer = new DataContractJsonSerializer(entity.GetType());
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, entity);
json = Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
}
var guid = GetDeterministicGuid(json);
eTagProperty.SetValue(entity, guid.ToString(), null);
//...
There are a couple of helper methods to check if the object has changed since the ETag value was last set, and to reset the ETag.
This implementation uses JSON to do the serializing rather than XML. Benefit - should be marginally more efficient as your hashing a much smaller serialized string; downside, JSON doesn't include namespaces or class names at the root level, so if you have two classes with the exact same structure but different names, then instances which have the same content will have the same ETag. You may want that behaviour, but change to use the XML DataContractSerializer if you think that will be an issue.
If you can persist the ETag somewhere, it will save you server processing to load up the entity, but that will only apply to scenarios where you can reliably invalidate your ETag (e.g. if you control all the entry points where entity contents can be updated, then you can calculate and persist the new ETag with each update).