Was this page helpful?

Getting Started with Contentful rich text and .NET

Pre-requisites

This tutorial assumes you understand the basic Contentful data model as described in the developer center and that you have already read and understand the getting started tutorial for the .NET SDK.

Contentful.net is built on .NET Core and targets .NET Standard 2.0. The SDK is cross-platform and runs on Linux, macOS and Windows.

What is rich text?

Rich text is a new JSON format for handling complex content structures in a strongly typed manner. It is represented by the rich text field in Contentful.

Working with a rich text property in the Contentful .NET SDK

Create a property in your model class of type Document.

public Document Body { get; set; }

The Document class contains a list of IContent which contains all the nodes of your rich text field deserialized into strongly typed classes. When you've fetched your entry that contains the rich text field you can iterate through this collection and use pattern matching to decide which action to take depending on the type of node.

var entry = await _client.GetEntries<SomeModel>();

foreach(IContent node in entry.Body.Content) {

  switch(node) {
    case Paragraph p:
      // Do something with the paragraph
    case Heading h:
      // Do something with the heading
    ...
  }
}

The most common use case is that you need to convert your rich text into HTML. The .NET SDK provides the HtmlRenderer class to facilitate this.

var entry = await _client.GetEntries<SomeModel>();
var htmlRenderer = new HtmlRenderer();

var html = htmlRenderer.ToHtml(entry.Body);

The html variable will now contain an HTML string representation of the content. The HtmlRenderer contains a list of renderers that can handle all of the node types that exist natively in the Contentful web app, except entries. If you wish to render entries or other custom node types you need to write your own IContentRenderer and add it to the HtmlRenderer via the AddRenderer method. The following is an example of a custom renderer that will render any embedded-entry-block.

public class CustomContentRenderer : IContentRenderer
        {
            public int Order { get; set; }

            public bool SupportsContent(IContent content)
            {
                return content is EntryStructure && (content as EntryStructure).NodeType == "embedded-entry-block";
            }

            public string Render(IContent content)
            {
                var model = (content as EntryStructure).Data.Target as SomeCustomModel;

                var sb = new StringBuilder();

                sb.Append("<div>");

                sb.Append($"<h2>{model.Title}</h2>");

                sb.Append("</div>");

                return sb.ToString();
            }
        }

This custom renderer can now be added to the HtmlRenderer.

var entry = await _client.GetEntries<SomeModel>();
var htmlRenderer = new HtmlRenderer();
htmlRenderer.Add(new CustomContentRenderer() { Order = 10 });

var html = htmlRenderer.ToHtml(entry.Body);

Note that the Order of the renderer is set to 10 to make sure it is added earlier in the rendering pipeline. The default order of the standard renderers are 100, except for the NullContentRenderer which has a default order of 500.

Working with a rich text property in an ASP.NET Core application

If you're working with ASP.NET Core the HtmlRenderer gets injected into the dependency injection pipeline and is available once you run services.AddContentful(Configuration) in your startup.cs.

There's also a tag helper that you can use directly in your Razor files to render a Document property to HTML.

  <contentful-rich-text document="@Model.Body" ></contentful-rich-text>

The tag helper will use the configured HtmlRenderer to render the provided Document directly to HTML.

If you have custom nodetypes or need to render entries, some additional setup is required to add the custom renderers to the HtmlRenderer pipeline.

 services.AddTransient((c) => {
      var renderer = new HtmlRenderer();
      renderer.AddRenderer(new CustomContentRenderer() { Order = 10 });
      return renderer;
  });

It's important to add any custom renderers after your services.AddContentful(Configuration) call and not before.

If you want your custom renderer to render a .cshtml view you can let your custom renderer inherit from the BaseContentRenderer. This gives your renderer the RenderToString method which takes the name of a view and a model to pass to the view.

public class CustomViewContentRenderer : BaseContentRenderer
    {
        public CustomViewContentRenderer(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) : base(razorViewEngine, tempDataProvider, serviceProvider)
        {
        }

        public override bool SupportsContent(IContent content)
        {
            return content is SomeModel;
        }

        public override string Render(IContent content)
        {
            var model = content as SomeModel;

            return RenderToString("SomeView", model);
        }
    }

As there are some services required to render a .cshtml view you also need to update the creation of the renderer in your startup.cs file.

  services.AddTransient((c) => {
      var renderer = new HtmlRenderer();
      renderer.AddRenderer(new CustomViewContentRenderer(c.GetService<IRazorViewEngine>(), c.GetService<ITempDataProvider>(), c) { Order = 10 });
      return renderer;
  });

Working with custom node types

If you have completely custom node types in your structure you will need to pattern match on the CustomNode type or write a custom renderer that handles the CustomNode type.

var entry = await _client.GetEntries<SomeModel>();

foreach(IContent node in entry.Body.Content) {

  switch(node) {
    case Paragraph p:
      // Do something with the paragraph
    case Heading h:
      // Do something with the heading
    case CustomNode c:
      // The c.JObject property will contain JObject representation of your custom node that you can serialize into an appropriate type.
      var model = c.JObject.ToObject<SomeModel>();
      // Do something with the model.
    ...
  }
}

A renderer to handle a custom node would also need to handle the CustomNode type and then deserialize the JObject into an appropriate type.

public class CustomViewContentRenderer : BaseContentRenderer
    {
        public CustomViewContentRenderer(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) : base(razorViewEngine, tempDataProvider, serviceProvider)
        {
        }

        public override bool SupportsContent(IContent content)
        {
            return content is CustomNode && (content as CustomNode).JObject["nodeType"].Value == "your-custom-node";
        }

        public override string Render(IContent content)
        {
            var model = (content as CustomNode).JObject.ToObject<SomeModel>;

            return RenderToString("SomeView", model);
        }
    }

If you want to inject your custom renderer into the HtmlRenderer available out of the box you also need to make sure the serialization engine knows how to deserialize your content type into a c# class. This is done by implementing an IContentTypeResolver and instructing the engine which type a certain content type would correspond to. Here is an example.

public class EntityResolver : IContentTypeResolver
    {
        public Dictionary<string, Type> _types = new Dictionary<string, Type>()
        {
            { "person", typeof(Person) },
            { "product", typeof(Product) },
        };

        public Type Resolve(string contentTypeId)
        {
            return _types.TryGetValue(contentTypeId, out var type) ? type : null;
        }
    }

This resolver then needs to be set on the client before fetching content.

_client.ContentTypeResolver = new EntityResolver();
var entry = await _client.GetEntries<SomeModel>();
var htmlRenderer = new HtmlRenderer();

var html = htmlRenderer.ToHtml(entry.Body);

The classes you expect to be part of a rich text structure must also implement the IContent interface. This means that any class you setup with the IContentTypeResolver should also ideally implement the IContent interface, which is a simple marker interface used to identify parts of a rich text structure.

Next steps

Not what you’re looking for? Try our FAQ.