> This page is for Contentful SDKs.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful-staging.ferndocs.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful-staging.ferndocs.com/developers/docs/_mcp/server.

# Getting Started with Contentful rich text and .NET

> This article details how to retrieve and manage entries with rich text using the .NET client library.

## Pre-requisites

This tutorial assumes you understand the basic Contentful data model as described in the [developer center](/concepts/data-model) and that you have already read and understand the [getting started tutorial for the .NET client library](/sdks/net/tutorials/getting-started-with-contentful-and-net).

Contentful.net is built on .NET Core and targets .NET Standard 2.0. The client library 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 client library

Create a property in your model class of type `Document`.

```csharp
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.

```csharp
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 client library provides the `HtmlRenderer` class to facilitate this.

```csharp
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`.

```csharp
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`.

```csharp
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.

```csharp
  <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.

To use custom taghelpers they need to be imported. Add `@addTagHelper *, Contentful.AspNetCore` to the `\_ViewImports.cshtml`

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

```csharp
 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 `RazorContentRenderer`. This gives your renderer the `RenderToString` method which takes the name of a view and a model to pass to the view.

```csharp
public class CustomViewContentRenderer : RazorContentRenderer
    {
        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.

```csharp
  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.

```csharp
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.

```csharp
public class CustomViewContentRenderer : RazorContentRenderer
    {
        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.

```csharp
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.

```csharp
_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

* [Explore the .NET client library GitHub repository](https://github.com/contentful/contentful.net)