Listeners: What are store listeners and how to work with them?

In order execute a custom action before or after a document is stored the IDocumentStoreListener needs to be implemented:

public interface IDocumentStoreListener
{
	/// <summary>
	/// Invoked before the store request is sent to the server.
	/// </summary>
	/// <param name="key">The key.</param>
	/// <param name="entityInstance">The entity instance.</param>
	/// <param name="metadata">The metadata.</param>
	/// <param name="original">The original document that was loaded from the server</param>
	/// <returns>
	/// Whatever the entity instance was modified and requires us re-serialize it.
	/// Returning true would force re-serialization of the entity, returning false would 
	/// mean that any changes to the entityInstance would be ignored in the current SaveChanges call.
	/// </returns>
	bool BeforeStore(string key, object entityInstance, RavenJObject metadata,
						RavenJObject original);

	/// <summary>
	/// Invoked after the store request is sent to the server.
	/// </summary>
	/// <param name="key">The key.</param>
	/// <param name="entityInstance">The entity instance.</param>
	/// <param name="metadata">The metadata.</param>
	void AfterStore(string key, object entityInstance, RavenJObject metadata);
}

Example

To prevent anyone from adding documents with a certain key, you can create FilterForbiddenKeysDocumentListener. In result the document with the specified forbidden id will not be stored.

public class FilterForbiddenKeysDocumentListener : IDocumentStoreListener
{
	private readonly IList<string> forbiddenKeys = new List<string> { "system" };

    public bool BeforeStore(string key, object entityInstance, RavenJObject metadata,
        RavenJObject original)
    {
        if (forbiddenKeys.Any(x => x.ToLower().Equals(key)))
        {
            throw new InvalidOperationException($"Store a document with a key: {key} is forbidden");
        }
        return false;
    }

    public void AfterStore(string key, object entityInstance, RavenJObject metadata)
	{
	}
}