I’ve been using both ServiceStack and RavenDB for building REST APIs for some time and it has been a great development experience. When Ravendb 4 came out, it made a change from the previous iterations of the product where in the id of the document being loaded now needed to be fully qualified, i.e. it had to have the name of the collection present. So an id would now be of the form: users/1-A This was not the case in earlier versions. To fix this well, we needed to ensure that the code that performed the prefix is in a single place as opposed to littered over the solution.

The best fix for this issue was to define a servicestack filter and let it do all the heavy lifting.

First, we define a simple interface for all our input models:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/// <summary>
/// Defines details for service models.
/// </summary>
public interface IServiceModel
{
    /// <summary>
    /// Gets or sets the id of the model.
    /// </summary>
    string Id { get; set; }

    /// <summary>
    /// Gets the name of the collection in which the model is stored.
    /// </summary>
    [IgnoreDataMember]
    string CollectionName { get; }
}

Every model now implements this interface. Let’s consider a simple user class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class User : IServiceModel
{
    /// <summary>
    /// Gets or sets the id of the user.
    /// </summary>
    [ApiMember(Description = "The id of the user.")]
    public string Id { get; set; }

    /// <summary>
    /// Gets the name of the collection.
    /// </summary>
    [IgnoreDataMember]
    public string CollectionName => Constants.UserCollectionName;

    /// <summary>
    /// Gets or sets the first name.
    /// </summary>
    [ApiMember(Description = "The first name of the user.")]
    public string FirstName { get; set; }

    /// <summary>
    /// Gets or sets the last name.
    /// </summary>
    [ApiMember(Description = "The last name of the user.")]
    public string LastName { get; set; }

    /// <summary>
    /// Gets or sets the email address.
    /// </summary>
    [ApiMember(Description = "The email address for the user.")]
    public string EmailAddress { get; set; }
}

Now the ServiceStack filter:

1
2
3
4
this.RegisterTypedRequestFilter<IServiceModel>((req, res, dtoInterface) =>
{
    dtoInterface.Id = dtoInterface.Id.IsNullOrEmpty() ? dtoInterface.Id : dtoInterface.GetFullyQualifiedRavenId();
});

And the extension method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/// <summary>
/// Returns the raven id for a specified model.
/// </summary>
/// <param name="entity">
/// The entity whose id is to be calculated.
/// </param>
/// <returns>
/// The fully qualified id for the object.
/// </returns>
public static string GetFullyQualifiedRavenId(this IServiceModel entity)
{
    var calculatedId = entity.Id.IndexOf(entity.CollectionName, StringComparison.Ordinal) == -1 ? $"{entity.CollectionName}/{entity.Id}" : entity.Id;
    return calculatedId;
}

This ensures that any code such as validators and actual service code which consume the id have the fully qualified id for use without having to pollute the codebase. This code is quite easy to extend for various situations such as searches.

An example for searches would be as below. We have a base class called SearchBase

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/// <summary>
/// Defines common parameters used for paging and sorting of
/// results.
/// </summary>
public class SearchBase : IHasIds, IHasExcludes
{
    /// <summary>
    /// Gets or sets the ids of items to be retrieved.
    /// </summary>
    [ApiMember(Description = "The ids of items to be returned by the search.")]
    public List<string> Ids { get; set; }

    /// <summary>
    /// Gets or sets the ids of items to be retrieved.
    /// </summary>
    [ApiMember(Description = "The ids of items to be excluded by the search.")]
    public List<string> Excludes { get; set; }
}

Any search inherits this class. Now to ensure that our ids are properly qualified:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
this.RegisterTypedRequestFilter<IHasIds>((req, res, dtoInterface) =>
{
    var searchType = req.Dto.GetType().Name;
    dtoInterface.Ids = dtoInterface.Ids.IsNullOrEmpty() ? dtoInterface.Ids : dtoInterface.Ids.Map(x => $"{searchType}/{x}");
});

this.RegisterTypedRequestFilter<IHasExcludes>((req, res, dtoInterface) =>
{
    var searchType = req.Dto.GetType().Name;
    dtoInterface.Excludes = dtoInterface.Excludes.IsNullOrEmpty() ? dtoInterface.Excludes : dtoInterface.Excludes.Map(x => $"{searchType}/{x}");
});

This is the simplest boilerplate. Never has to be thought of for the existence of the solution as long as the right interface or base class is inherited.