This blog post explains how you can use .NET attributes to indicate entry model class(es) to represent entries based on different content types in the Contentstack SaaS headless CMS, although this technique should work with any headless CMS.
Update 15.Dec.2025: After years of frustration with WordPress, I am finally abandoning this blog. The content will likely stay here for some time, but new content will appear here:
When you need to retrieve an instance of an entry model (an object that represents an entry from the CMS), something needs to determine which class to use to represent that entry. There are many possible strategies for this logic, such as conventions based on matching content types to class names. I mark entry model classes with an attribute that identifies the content type(s) for which it is relevant. The logic for retrieving entry models consults these attributes to determine which class to instantiate.
Because other attributes could need to scan assemblies to determine types to which they apply, I put that logic in a base class for attributes.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
public abstract class AttributeBase : Attribute
{
// map the type of the attribute to a list of types that have that attribute.
// static and cached to optimize performance.
private static ConcurrentDictionary<Type, List<Type>> _types =
new ConcurrentDictionary<Type, List<Type>>();
public static Type[] GetTypesWithAttribute(Type attribute)
{
// Have assemblies already been scanned for this attribute.
if (!_types.ContainsKey(attribute))
{
// no, create a new entry in the dictionary mapping
// the attribute to the list of types that have that attribute.
List<Type> result = new List<Type>();
// for each assembly available
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
// for each type in the assembly
foreach (Type type in assembly.GetTypes())
{
// if the type has the attribute
if (type.GetCustomAttributes(attribute, true).Length > 0)
{
// then add it to the list of types for the attribute
result.Add(type);
}
}
}
catch (Exception ex)
{
// ignore; unable to load; assembly may be obfuscated, etc.
}
}
// store a record mapping the attribute to the list of types with that attribute
_types[attribute] = result;
}
// return the list of types with the attribute
return _types[attribute].ToArray();
}
}
A derived attribute class associates entry models with content types.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ContentTypeIdentifier : AttributeBase
{
public ContentTypeIdentifier(string value)
{
Value = value;
}
public string Value { get; }
private static ConcurrentDictionary<string, Type[]> _contentTypeTypes =
new ConcurrentDictionary<string, Type[]>();
public static Type[] GetModelTypeForContentType(string contentType)
{
if (!_contentTypeTypes.ContainsKey(contentType))
{
List<Type> result = new List<Type>();
foreach (Type t in ContentTypeIdentifier.GetTypesWithAttribute(
typeof(ContentTypeIdentifier)))
{
foreach (var attr in t.GetCustomAttributes(
typeof(ContentTypeIdentifier), true))
{
if (attr is ContentTypeIdentifier ct
&& ct.Value.Equals(contentType,
StringComparison.InvariantCultureIgnoreCase))
{
result.Add(t);
}
}
}
_contentTypeTypes[contentType] = result.ToArray();
}
return _contentTypeTypes[contentType];
}
}
Now I can attribute the entry models to associate them with content types.
[ContentTypeIdentifier("modularblocksexample")]
public class ExampleBlocksEntryModel : EntryModelBase
I need to add a signature to my typed client that retrieves entries from the CMS as well as its underlying IContentDeliveryClient interface.
public async Task<EntryModelBase> AsDefaultEntryModelAsync(EntryIdentifier entryId)
{
Type type = typeof(EntryModelBase);
Type[] types = ContentTypeIdentifier.GetModelTypeForContentType(entryId.ContentType);
if (types.Length > 0)
{
type = types[0];
}
JObject json = await AsObjectAsync(entryId);
EntryModelBase entryModel = (EntryModelBase) json.SelectToken("$.entry").ToObject(type);
entryModel.Json = json;
return entryModel;
}
2 thoughts on “-Use .NET Attributes to Specify Entry Model Classes for CMS Content Types”