Including referenced assets and entries
When querying for entries it's very common that the returned entries reference other assets and entries. For example a blog post entry could reference its author entry and vice versa.
You could of course get these two entries separately, but that would mean two http requests going over the wire. Http calls should be kept to a minimum. Not only to keep the number of api calls down, but more importantly to increase the responsiveness and performance of your application. Ideally you would like to get the referenced entries in the same request. Contentfuls APIs allow you to do this by specifying how many levels of references you wish to resolve and include in the api response.
Consider the following classes.
public class BlogPost
{
public string Title { get; set; }
public string Slug { get; set; }
public string Body { get; set; }
public List<string> Tags { get; set; }
public DateTime Date { get; set; }
public List<Author> Authors { get; set; }
public Asset FeaturedImage { get; set; }
public List<Asset> Images { get; set; }
}
public class Author {
public string Name { get; set; }
}
This blog post has three different properties that contain references to other assets and entries. How do we make sure to resolve these correctly without making an uneccessary amount of http requests?
Specifying the number of levels to include
To specify the number of levels to include in a call simply add an include querystring parameter, either manually or preferably by using the QueryBuilder.
var builder = new QueryBuilder().ContentTypeIs("ContentTypeId").Include(3);
var entries = await client.GetEntriesAsync<BlogPost>(builder);
This querys for entries of a specific content type id and tells the Contentful API to resolve up to three levels of referenced entries and assets. The default setting for the include paramenter is 1. This means that omitting the query string parameter still resolves up to 1 level of referenced content. If you specifically do not want any referenced content to be included you need to set the include parameter to 0.
var builder = new QueryBuilder().ContentTypeIs("ContentTypeId").Include(0);
var entries = await client.GetEntriesAsync<BlogPost>(builder);
//No referenced content would be included.
Note that including referenced content is only supported for the methods that return collections. If you use GetEntryAsync
your references will not be resolved. To get around this
you could query for a single entry using GetEntriesAsync
instead, but adding a restriction to only get an entry by a specific id.
var builder = new QueryBuilder().FieldEquals("sys.id", "123").Include(2);
var entry = await client.GetEntriesAsync<BlogPost>(builder).FirstOrDefault();
This would fetch an entry with id "123" and include up to two levels of referenced entries and assets.
Resolving included assets
To resolve assets when querying for content simply add a property of type Asset
or IEnumerable<Asset>
and the deserialization will automatically fill up any referenced assets.
var builder = new QueryBuilder().ContentTypeIs("ContentTypeId").Include(1);
var entries = await client.GetEntriesAsync<BlogPost>(builder);
Console.WriteLine(entries.First().FeaturedImage.Title); // => Alice in Wonderland
Console.WriteLine(entries.First().Images.Count.ToString()); // => 2
Resolving included entries
Entries are similarily simple to resolve if you use the generic Entry<T>
class. Consider that we change the Authors property of our BlogPost class above like this.
public List<Entry<Author>> Authors { get; set; }
Our referenced authors would now be correctly deserialized and included in our BlogPost
.
var builder = new QueryBuilder().ContentTypeIs("ContentTypeId").Include(1);
var entries = await client.GetEntriesAsync<BlogPost>(builder);
Console.WriteLine(entries.First().Authors[0].Fields.Name); // => Lewis Carroll
Console.WriteLine(entries.First().Authors[0].SystemProperties.Id); // => 1234
If we're not interested in our meta data about the entry we might be tempted to change the property back to this.
public List<Author> Authors { get; set; }
Unfortunately this will not work right away. Since the structure of an entry returned from Contentful consists of two properties sys
and fields
there is no way to serialize this
into our Author
class right away. However if you decorate the Author
class with a converter attribute telling we can get around this. If we were to add an EntryFieldJsonConverter
to the Author
class like this.
[JsonConverter(typeof(EntryFieldJsonConverter))]
public class Author {
public string Name { get; set; }
}
The Author
class would now be serialized directly from the fields
property of the JSON response and we could now skip the use of Entry<T>
in our BlogPost
.
var builder = new QueryBuilder().ContentTypeIs("ContentTypeId").Include(1);
var entries = await client.GetEntriesAsync<BlogPost>(builder);
Console.WriteLine(entries.First().Authors[0].Name); // => Lewis Carroll
//Compare the above line with Console.WriteLine(entries.First().Authors[0].Fields.Name);
Console.WriteLine(entries.First().Authors[0].SystemProperties.Id); // => This no longer compiles as Author does not contain SystemProperties