How to treat your content model as code using .NET and Contentful

Today we're pleased to show you a code first alternative for .NET developers. This release makes it easier for you to keep your content infrastructure within your code base.

We call this initiative Contentful.Codefirst and you can install it using NuGet: nuget install-package Contentful.CodeFirst.

This release solves the challenge that you sometimes face when using a headless CMS and need to unify your content model across environments. Because sometimes changes that that you made in the staging environment might be overlooked when you ship your code to production.

Note: This project is independent from the Contentful Migration CLI which proposes a solution for scripting content model changes.

Getting started

To get started you create a class that will represent your content type in Contentful and decorate it with a ContentType attribute.

1
2
3
4
[ContentType]
public class BlogPost {
    public string Title { get; set; }
}

Then call the CreateContentTypesFromAssembly method with a configuration object containing your space id and API key.

1
2
3
4
5
6
7
var configuration = new ContentfulCodeFirstConfiguration 
{
  ApiKey = "<management_api_key>",
  SpaceId = "<space_id>"
};

var createdContentTypes = ContentTypeBuilder.CreateContentTypesFromAssembly("AssemblyName", configuration);

The “AssemblyName” argument is the name of the assembly where the BlogPost class exists.

Preferably, you will make this call at application launch — and the content types will be automatically created for you. In the case outlined above, the result looks like this.

code-first-blog-post (1)

You might have noticed that the created content type does not have any description, that you did not get to set the displayfield and the created property is of type long text. Fortunately, you can control all of these settings from your code.

1
2
3
4
5
6
[ContentType(Description = "A simple blog post example", DisplayField = "Title")]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Title { get; set; }
}

As you can see, there's an attribute to use for fields if you need to control their properties as well. You can also specify the id and the name of the content type if you're dissatisfied with the defaults.

1
2
3
4
5
6
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Id="anotherId", Name = "Blog by another name")]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Title { get; set; }
}

Finally, you can also set the order property which controls the order in which content types are processed. This can be important for content types that reference other content types as the referenced content type then needs to be created before the one referencing it.

1
2
3
4
5
6
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Order = 25)]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Title { get; set; }
}

The ContentField attribute similarly contains several properties you can use to control what type of field gets created in Contentful.

1
2
[ContentField(Type = SystemFieldTypes.Symbol, Id = "customFieldId", Localized = true, Name = "Title field", Omitted = false, Required = true)]
public string Title { get; set; }

You can set the id of the field as with a content type. You can decide whether the field should be localized or not, omitted from the API response and/or required. You can also set a custom name for the field.

Another important concept in Contentful is field appearances, which allows you to specify how a certain field should appear and behave in the Contentful web app. You can for example specify that a long string should appear as either a markdown editor, a plain input field or a multiline textbox. In Contentful.Codefirst you do this by adding another attribute to your property.

1
2
3
[ContentField(Type = SystemFieldTypes.Symbol, Id = "customFieldId", Localized = true, Name = "Title field", Omitted = false, Required = true)]
[FieldAppearance(SystemWidgetIds.SingleLine, "This is the helptext!")]
public string Title { get; set; }

The FieldAppearance attribute has two properties, the extension id and the helptext. The extension id should be set to one of the system extensions or one of your custom ones. You can read more about this concept and find a complete list of extensions available here. The helptext is displayed below the field to give the editor a hint on how to use it.

There are also specific attributes for a few field appearances as they have custom configuration. RatingAppearance, BooleanAppearance and DatePickerAppearance. Here's an example of the BooleanAppearance.

1
2
[BooleanAppearance(trueLabel: "Yes", falseLabel: "No", helpText: "Helptext for the field")]
public boolean ShouldDoSomething { get; set; }

In this way, you can control the settings and appearances of fields — but how about validations?

Validations can be added by using the validation attributes Size, Range, LinkContentType, InValues, MimeType, Regex, Unique, DateRange, FileSize and ImageSize. You apply them to a property and the validation gets added to Contentful.

1
2
3
4
5
6
7
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Order = 25)]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   [Unique]
   public string Title { get; set; }
}

To create multiple content types which reference each other you create multiple classes and take care adding the order property to make sure they are created in the correct order. Here we’ve added Author to our blog post.

1
2
3
4
5
6
7
8
9
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Order = 25)]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   [Unique]
   public string Title { get; set; }

   public Author Author { get; set; }
}

And you, of course, need to create the actual Author class.

1
2
3
4
5
6
[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }
}

This will create a reference field by the name of Author, but it will currently accept any type of entry. To restrict it to only accept Author content types we use a validation attribute.

1
2
3
4
5
6
7
8
9
10
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Order = 25)]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   [Unique]
   public string Title { get; set; }

   [LinkContentType(nameof(Author))]
   public Author Author { get; set; }
}

Adding assets is equally straightforward.

1
2
3
4
5
6
7
8
[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }

   public Asset Photo { get; set; }
}

Making sure only a certain type of asset can be added is, again, done with a validation attribute.

1
2
3
4
5
6
7
8
9
[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }

   [MimeType(MimeTypes = new [] { MimeTypeRestriction.Image })]
   public Asset Photo { get; set; }
}

Advanced techniques and gotchas

It's important to understand that Contentful.Codefirst, while powerful, is nothing magical. All it does, under the hood, is call the Contentful API. This means that any requirement of the API is also a requirement of Contentful.Codefirst.

Deleting fields is one example that can seem slightly counter intuitive at first. If you remove a property of a class that has already been created as a content type in Contentful, an exception is thrown.

When you remove the property of a class, Contentful.Codefirst will try to remove that field from the content type in Contentful. You are not allowed to remove a field that has not first been omitted from the API response. This is very important to avoid unintentional data loss. Once a field is deleted so is all data related to that field. Therefore you must first omit a field before deleting it.

1
2
3
4
5
6
7
8
9
10
[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }

   [MimeType(MimeTypes = new [] { MimeTypeRestriction.Image })]
   [ContentField(Omitted = true)]
   public Asset Photo { get; set; }
}

Once a field has been omitted, you can safely delete it from your content type. Do remember that all data associated with the field will be deleted as well!

The display field of a content type can never be deleted, unless you specify another field as the display field.

Circular references is another tricky concept. If you have two content types that need to reference each other, you need to first create one of them without the content type restriction set, then update it once both have been created. Consider the following example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ContentType(Description = "A simple blog post example", DisplayField = "Title", Order = 25)]
public class BlogPost
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   [Unique]
   public string Title { get; set; }

   [LinkContentType(nameof(Author))]
   public Author Author { get; set; }
}

[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }

   public BlogPost Posts { get; set; }
}

You create both content types, but only the property of the BlogPost has a LinkContentType validation. Once you've run this code, and both content types have been created, you can add the validation to the Author class as well.

1
2
3
4
5
6
7
8
9
[ContentType(Description = "The author of a blog post", DisplayField = "Name", Order = 10)]
public class Author
{
   [ContentField(Type = SystemFieldTypes.Symbol)]
   public string Name { get; set; }

   [LinkContentType(nameof(BlogPost))]
   public BlogPost Posts { get; set; }
}

Where to go from here?

This was a quick introduction to how you can treat your content model as code in .NET. You can also check out the code on github if you want to contribute to the project or simply browse through the source.

We also invite you to discuss this release, and everything else related to Contentful, over at Contentful Community

Happy coding!

Blog posts in your inbox

Subscribe to receive most important updates. We send emails once a month.