> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET

> Build content-driven .NET apps with ButterCMS. Covers setup, pages, collections, components, and blog content.

## Overview

This integration guide shows you how to how to update your existing project to:

1. install the ButterCMS package
2. instantiate ButterCMS
3. create components to fetch and display each of the three ButterCMS content types: [Pages](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

### Starter project

Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working
starter project that's integrated with content already inside of your ButterCMS account.

<Card title=".NET Starter Project" icon="rocket" href="../starter-projects/dotnet">
  Hit the ground running with a pre-configured .NET + ButterCMS setup.
</Card>

## Installation

<Tabs>
  <Tab title=".NET CLI">
    ```bash theme={null}
    dotnet add package ButterCMS
    ```
  </Tab>

  <Tab title="Package Manager">
    ```bash theme={null}
    Install-Package ButterCMS
    ```
  </Tab>

  <Tab title="PackageReference">
    ```xml theme={null}
    <PackageReference Include="ButterCMS" Version="2.*" />
    ```
  </Tab>
</Tabs>

## Initialize the client

<Tabs>
  <Tab title="ASP.NET Core MVC">
    ```csharp theme={null}
    // Program.cs
    using ButterCMS;

    var builder = WebApplication.CreateBuilder(args);

    // Register ButterCMS client
    builder.Services.AddSingleton<ButterCMSClient>(sp =>
        new ButterCMSClient(builder.Configuration["ButterCMS:ApiToken"]));

    builder.Services.AddControllersWithViews();

    var app = builder.Build();

    app.UseStaticFiles();
    app.UseRouting();
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    app.Run();
    ```
  </Tab>

  <Tab title="Minimal API">
    ```csharp theme={null}
    // Program.cs
    using ButterCMS;

    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddSingleton<ButterCMSClient>(sp =>
        new ButterCMSClient(builder.Configuration["ButterCMS:ApiToken"]));

    var app = builder.Build();

    // Routes defined below
    app.Run();
    ```
  </Tab>
</Tabs>

```json theme={null}
// appsettings.json
{
  "ButterCMS": {
    "ApiToken": "your_api_token"
  }
}
```

<Info>
  For complete SDK documentation including all available methods and configuration options, see the [.NET SDK Reference](../sdks/dotnet-sdk).
</Info>

## Data models

```csharp theme={null}
// Models/ButterModels.cs
namespace YourApp.Models;

public class LandingPageFields
{
    public string Headline { get; set; } = "";
    public string Subheadline { get; set; } = "";
    public string HeroImage { get; set; } = "";
    public string Body { get; set; } = "";
    public SEOFields? Seo { get; set; }
    public List<ComponentData>? BodyComponents { get; set; }
}

public class SEOFields
{
    public string Title { get; set; } = "";
    public string Description { get; set; } = "";
    public string OgTitle { get; set; } = "";
    public string OgDescription { get; set; } = "";
    public string OgImage { get; set; } = "";
}

public class BrandCollection
{
    public List<Brand> Brands { get; set; } = new();
}

public class Brand
{
    public string Name { get; set; } = "";
    public string Logo { get; set; } = "";
    public string Description { get; set; } = "";
}

public class ComponentData
{
    public string Type { get; set; } = "";
    public Dictionary<string, object> Fields { get; set; } = new();
}

public class BlogIndexViewModel
{
    public IEnumerable<ButterCMS.Models.Post> Posts { get; set; } = new List<ButterCMS.Models.Post>();
    public ButterCMS.Models.PostsMeta Meta { get; set; } = new();
}
```

## Pages

<Tabs>
  <Tab title="ASP.NET Core MVC">
    ```csharp theme={null}
    // Controllers/PageController.cs
    using Microsoft.AspNetCore.Mvc;
    using ButterCMS;
    using YourApp.Models;

    public class PageController : Controller
    {
        private readonly ButterCMSClient _butter;

        public PageController(ButterCMSClient butter)
        {
            _butter = butter;
        }

        [HttpGet("/")]
        [HttpGet("/page/{slug}")]
        public async Task<IActionResult> Show(string slug = "home")
        {
            try
            {
                var response = await _butter.RetrievePageAsync<LandingPageFields>(
                    "landing_page", slug);
                return View("Landing", response.Data);
            }
            catch
            {
                return NotFound();
            }
        }

        [HttpGet("/pages")]
        public async Task<IActionResult> List()
        {
            var response = await _butter.ListPagesAsync<LandingPageFields>("landing_page");
            return View(response.Data);
        }
    }
    ```

    ```cshtml theme={null}
    @* Views/Page/Landing.cshtml *@
    @model ButterCMS.Models.Page<LandingPageFields>

    @{
        ViewData["Title"] = Model.Fields.Seo?.Title ?? Model.Fields.Headline;
        ViewData["Description"] = Model.Fields.Seo?.Description ?? "";
    }

    <main>
        <h1>@Model.Fields.Headline</h1>
        <p>@Model.Fields.Subheadline</p>

        @if (!string.IsNullOrEmpty(Model.Fields.HeroImage))
        {
            <img src="@Model.Fields.HeroImage" alt="@Model.Fields.Headline" />
        }

        @Html.Raw(Model.Fields.Body)
    </main>
    ```
  </Tab>

  <Tab title="Minimal API">
    ```csharp theme={null}
    // Program.cs
    app.MapGet("/api/pages/{type}/{slug}", async (
        string type, string slug, ButterCMSClient butter) =>
    {
        try
        {
            var response = await butter.RetrievePageAsync<LandingPageFields>(type, slug);
            return Results.Ok(response.Data);
        }
        catch
        {
            return Results.NotFound();
        }
    });

    app.MapGet("/api/pages/{type}", async (
        string type, ButterCMSClient butter) =>
    {
        var response = await butter.ListPagesAsync<LandingPageFields>(type);
        return Results.Ok(response.Data);
    });
    ```
  </Tab>
</Tabs>

## Collections

<Tabs>
  <Tab title="ASP.NET Core MVC">
    ```csharp theme={null}
    // Controllers/BrandController.cs
    using Microsoft.AspNetCore.Mvc;
    using ButterCMS;
    using YourApp.Models;

    public class BrandController : Controller
    {
        private readonly ButterCMSClient _butter;

        public BrandController(ButterCMSClient butter)
        {
            _butter = butter;
        }

        [HttpGet("/brands")]
        public async Task<IActionResult> Index()
        {
            var response = await _butter.RetrieveContentFieldsAsync<BrandCollection>(
                new[] { "brands" });
            return View(response.Data.Brands);
        }
    }
    ```

    ```cshtml theme={null}
    @* Views/Brand/Index.cshtml *@
    @model List<Brand>

    <main>
        <h1>Our Brands</h1>
        <ul>
            @foreach (var brand in Model)
            {
                <li>
                    <img src="@brand.Logo" alt="@brand.Name" />
                    <h2>@brand.Name</h2>
                    @Html.Raw(brand.Description)
                </li>
            }
        </ul>
    </main>
    ```
  </Tab>

  <Tab title="Minimal API">
    ```csharp theme={null}
    // Program.cs
    app.MapGet("/api/collections/{key}", async (
        string key, ButterCMSClient butter) =>
    {
        var response = await butter.RetrieveContentFieldsAsync<BrandCollection>(
            new[] { key });
        return Results.Ok(response.Data);
    });
    ```
  </Tab>
</Tabs>

## Dynamic components

### Component models

```csharp theme={null}
// Models/Components.cs
namespace YourApp.Models;

public class HeroFields
{
    public string Headline { get; set; } = "";
    public string Subheadline { get; set; } = "";
    public string Image { get; set; } = "";
    public string ButtonLabel { get; set; } = "";
    public string ButtonUrl { get; set; } = "";
}

public class FeaturesFields
{
    public string Headline { get; set; } = "";
    public List<FeatureItem> Items { get; set; } = new();
}

public class FeatureItem
{
    public string Title { get; set; } = "";
    public string Description { get; set; } = "";
    public string Icon { get; set; } = "";
}

public class CTAFields
{
    public string Headline { get; set; } = "";
    public string Subheadline { get; set; } = "";
    public string ButtonLabel { get; set; } = "";
    public string ButtonUrl { get; set; } = "";
}
```

### Component renderer

```csharp theme={null}
// Services/ComponentRenderer.cs
using System.Text;
using System.Text.Json;
using YourApp.Models;

public static class ComponentRenderer
{
    public static string Render(List<ComponentData> components)
    {
        var sb = new StringBuilder();

        foreach (var component in components)
        {
            sb.Append(component.Type switch
            {
                "hero" => RenderHero(component.Fields),
                "features" => RenderFeatures(component.Fields),
                "cta" => RenderCTA(component.Fields),
                _ => ""
            });
        }

        return sb.ToString();
    }

    private static string RenderHero(Dictionary<string, object> fields)
    {
        var headline = fields.GetValueOrDefault("headline", "")?.ToString() ?? "";
        var subheadline = fields.GetValueOrDefault("subheadline", "")?.ToString() ?? "";
        var image = fields.GetValueOrDefault("image", "")?.ToString() ?? "";
        var buttonLabel = fields.GetValueOrDefault("button_label", "")?.ToString() ?? "";
        var buttonUrl = fields.GetValueOrDefault("button_url", "")?.ToString() ?? "";

        return $@"
            <section class=""hero"">
                <h1>{headline}</h1>
                <p>{subheadline}</p>
                {(string.IsNullOrEmpty(buttonLabel) ? "" : $@"<a href=""{buttonUrl}"" class=""btn"">{buttonLabel}</a>")}
                {(string.IsNullOrEmpty(image) ? "" : $@"<img src=""{image}"" alt=""{headline}"" />")}
            </section>";
    }

    private static string RenderFeatures(Dictionary<string, object> fields)
    {
        var headline = fields.GetValueOrDefault("headline", "")?.ToString() ?? "";
        var items = fields.GetValueOrDefault("items") as JsonElement? ?? default;

        var itemsHtml = new StringBuilder();
        if (items.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in items.EnumerateArray())
            {
                var title = item.GetProperty("title").GetString() ?? "";
                var description = item.GetProperty("description").GetString() ?? "";
                itemsHtml.Append($@"
                    <div class=""feature"">
                        <h3>{title}</h3>
                        <p>{description}</p>
                    </div>");
            }
        }

        return $@"
            <section class=""features"">
                <h2>{headline}</h2>
                <div class=""features-grid"">{itemsHtml}</div>
            </section>";
    }

    private static string RenderCTA(Dictionary<string, object> fields)
    {
        var headline = fields.GetValueOrDefault("headline", "")?.ToString() ?? "";
        var subheadline = fields.GetValueOrDefault("subheadline", "")?.ToString() ?? "";
        var buttonLabel = fields.GetValueOrDefault("button_label", "")?.ToString() ?? "";
        var buttonUrl = fields.GetValueOrDefault("button_url", "")?.ToString() ?? "";

        return $@"
            <section class=""cta"">
                <h2>{headline}</h2>
                <p>{subheadline}</p>
                <a href=""{buttonUrl}"" class=""btn"">{buttonLabel}</a>
            </section>";
    }
}
```

### Example component

```cshtml theme={null}
@* Views/Shared/Components/Hero.cshtml *@
@model HeroFields

<section class="hero">
    <h1>@Model.Headline</h1>
    <p>@Model.Subheadline</p>

    @if (!string.IsNullOrEmpty(Model.ButtonLabel))
    {
        <a href="@Model.ButtonUrl" class="btn">@Model.ButtonLabel</a>
    }

    @if (!string.IsNullOrEmpty(Model.Image))
    {
        <img src="@Model.Image" alt="@Model.Headline" />
    }
</section>
```

### Using in pages

<Tabs>
  <Tab title="ASP.NET Core MVC">
    ```csharp theme={null}
    // Controllers/ComponentPageController.cs
    public class ComponentPageController : Controller
    {
        private readonly ButterCMSClient _butter;

        public ComponentPageController(ButterCMSClient butter)
        {
            _butter = butter;
        }

        [HttpGet("/landing/{slug}")]
        public async Task<IActionResult> Show(string slug)
        {
            try
            {
                var response = await _butter.RetrievePageAsync<dynamic>(
                    "landing_page", slug);

                var page = response.Data;
                var bodyComponents = new List<ComponentData>();

                // Parse components from body field
                if (page.Fields.body != null)
                {
                    foreach (var comp in page.Fields.body)
                    {
                        bodyComponents.Add(new ComponentData
                        {
                            Type = comp.type.ToString(),
                            Fields = JsonSerializer.Deserialize<Dictionary<string, object>>(
                                comp.fields.ToString())
                        });
                    }
                }

                ViewBag.Components = ComponentRenderer.Render(bodyComponents);
                return View("ComponentPage", page);
            }
            catch
            {
                return NotFound();
            }
        }
    }
    ```

    ```cshtml theme={null}
    @* Views/ComponentPage/ComponentPage.cshtml *@
    @model dynamic

    <main>
        @Html.Raw(ViewBag.Components)
    </main>
    ```
  </Tab>

  <Tab title="Minimal API">
    ```csharp theme={null}
    // Program.cs
    app.MapGet("/api/landing/{slug}", async (
        string slug, ButterCMSClient butter) =>
    {
        try
        {
            var response = await butter.RetrievePageAsync<dynamic>("landing_page", slug);
            return Results.Ok(response.Data);
        }
        catch
        {
            return Results.NotFound();
        }
    });
    ```
  </Tab>
</Tabs>

## Blog

<Tabs>
  <Tab title="Blog List">
    <Tabs>
      <Tab title="ASP.NET Core MVC">
        ```csharp theme={null}
        // Controllers/BlogController.cs
        using Microsoft.AspNetCore.Mvc;
        using ButterCMS;
        using YourApp.Models;

        public class BlogController : Controller
        {
            private readonly ButterCMSClient _butter;

            public BlogController(ButterCMSClient butter)
            {
                _butter = butter;
            }

            [HttpGet("/blog")]
            public async Task<IActionResult> Index(int page = 1)
            {
                var response = await _butter.ListPostsAsync(page, 10);
                return View(new BlogIndexViewModel
                {
                    Posts = response.Data,
                    Meta = response.Meta
                });
            }

            [HttpGet("/blog/category/{slug}")]
            public async Task<IActionResult> Category(string slug, int page = 1)
            {
                var response = await _butter.ListPostsAsync(page, 10,
                    categorySlug: slug);
                return View("Index", new BlogIndexViewModel
                {
                    Posts = response.Data,
                    Meta = response.Meta
                });
            }
        }
        ```

        ```cshtml theme={null}
        @* Views/Blog/Index.cshtml *@
        @model BlogIndexViewModel

        <main>
            <h1>Blog</h1>
            <ul>
                @foreach (var post in Model.Posts)
                {
                    <li>
                        <h2><a asp-action="Show" asp-route-slug="@post.Slug">@post.Title</a></h2>
                        <p class="meta">
                            By @post.Author.FirstName @post.Author.LastName
                            on @post.Published?.ToString("MMMM d, yyyy")
                        </p>
                        <p>@post.Summary</p>
                    </li>
                }
            </ul>

            @if (Model.Meta.NextPage.HasValue)
            {
                <a asp-action="Index" asp-route-page="@Model.Meta.NextPage">Next Page</a>
            }
        </main>
        ```
      </Tab>

      <Tab title="Minimal API">
        ```csharp theme={null}
        // Program.cs
        app.MapGet("/api/posts", async (
            int page, int pageSize, ButterCMSClient butter) =>
        {
            var response = await butter.ListPostsAsync(page, pageSize);
            return Results.Ok(new { data = response.Data, meta = response.Meta });
        });

        app.MapGet("/api/posts/category/{slug}", async (
            string slug, int page, int pageSize, ButterCMSClient butter) =>
        {
            var response = await butter.ListPostsAsync(page, pageSize, categorySlug: slug);
            return Results.Ok(new { data = response.Data, meta = response.Meta });
        });
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Blog Post">
    <Tabs>
      <Tab title="ASP.NET Core MVC">
        ```csharp theme={null}
        // Controllers/BlogController.cs
        [HttpGet("/blog/{slug}")]
        public async Task<IActionResult> Show(string slug)
        {
            try
            {
                var response = await _butter.RetrievePostAsync(slug);
                return View(response.Data);
            }
            catch
            {
                return NotFound();
            }
        }
        ```

        ```cshtml theme={null}
        @* Views/Blog/Show.cshtml *@
        @model ButterCMS.Models.Post

        @{
            ViewData["Title"] = Model.SeoTitle ?? Model.Title;
            ViewData["Description"] = Model.MetaDescription ?? Model.Summary;
        }

        <article>
            <h1>@Model.Title</h1>
            <p class="meta">
                By @Model.Author.FirstName @Model.Author.LastName
                on @Model.Published?.ToString("MMMM d, yyyy")
            </p>

            @if (!string.IsNullOrEmpty(Model.FeaturedImage))
            {
                <img src="@Model.FeaturedImage" alt="@Model.Title" />
            }

            @Html.Raw(Model.Body)

            @if (Model.Categories?.Any() == true)
            {
                <div class="categories">
                    Categories:
                    @foreach (var category in Model.Categories)
                    {
                        <a asp-action="Category" asp-route-slug="@category.Slug">@category.Name</a>
                    }
                </div>
            }

            <a asp-action="Index">Back to Posts</a>
        </article>
        ```
      </Tab>

      <Tab title="Minimal API">
        ```csharp theme={null}
        // Program.cs
        app.MapGet("/api/posts/{slug}", async (string slug, ButterCMSClient butter) =>
        {
            try
            {
                var response = await butter.RetrievePostAsync(slug);
                return Results.Ok(response.Data);
            }
            catch
            {
                return Results.NotFound();
            }
        });
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Preview mode

```csharp theme={null}
// Controllers/PageController.cs
[HttpGet("/page/{slug}")]
public async Task<IActionResult> Show(string slug = "home", bool preview = false)
{
    try
    {
        var parameters = new Dictionary<string, string>();
        if (preview)
        {
            parameters["preview"] = "1";
        }

        var response = await _butter.RetrievePageAsync<LandingPageFields>(
            "landing_page", slug, parameters);

        ViewBag.IsPreview = preview;
        return View("Landing", response.Data);
    }
    catch
    {
        return NotFound();
    }
}
```

## Caching

```csharp theme={null}
// Services/CachedButterService.cs
using Microsoft.Extensions.Caching.Memory;
using ButterCMS;

public class CachedButterService
{
    private readonly ButterCMSClient _butter;
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _defaultTtl = TimeSpan.FromMinutes(60);

    public CachedButterService(ButterCMSClient butter, IMemoryCache cache)
    {
        _butter = butter;
        _cache = cache;
    }

    public async Task<ButterCMS.Models.Page<T>> GetPageAsync<T>(
        string pageType, string slug, bool preview = false)
    {
        if (preview)
        {
            var previewResponse = await _butter.RetrievePageAsync<T>(
                pageType, slug, new Dictionary<string, string> { { "preview", "1" } });
            return previewResponse.Data;
        }

        var cacheKey = $"page_{pageType}_{slug}";

        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _defaultTtl;
            var response = await _butter.RetrievePageAsync<T>(pageType, slug);
            return response.Data;
        }) ?? throw new Exception("Page not found");
    }

    public async Task<IEnumerable<ButterCMS.Models.Post>> GetPostsAsync(int page, int pageSize)
    {
        var cacheKey = $"posts_{page}_{pageSize}";

        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = _defaultTtl;
            var response = await _butter.ListPostsAsync(page, pageSize);
            return response.Data;
        }) ?? Enumerable.Empty<ButterCMS.Models.Post>();
    }

    public void InvalidateCache(string pattern)
    {
        // For IMemoryCache, you'd need to track keys or use a distributed cache
        // This example shows the concept
    }
}
```

### Webhook cache invalidation

```csharp theme={null}
// Controllers/WebhookController.cs
[ApiController]
[Route("api/webhooks")]
public class WebhookController : ControllerBase
{
    private readonly CachedButterService _butterService;

    public WebhookController(CachedButterService butterService)
    {
        _butterService = butterService;
    }

    [HttpPost("butter")]
    public IActionResult HandleButterWebhook([FromBody] WebhookPayload payload)
    {
        // Invalidate cache based on webhook event
        if (payload.Webhook?.Event?.Contains("page") == true)
        {
            _butterService.InvalidateCache("page_");
        }

        if (payload.Webhook?.Event?.Contains("post") == true)
        {
            _butterService.InvalidateCache("posts_");
        }

        return Ok(new { status = "ok" });
    }
}

public class WebhookPayload
{
    public WebhookData? Webhook { get; set; }
}

public class WebhookData
{
    public string? Event { get; set; }
}
```

## SEO

```cshtml theme={null}
@* Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html>
<head>
    <title>@ViewData["Title"] - My Site</title>
    <meta name="description" content="@ViewData["Description"]">

    <!-- Open Graph -->
    <meta property="og:title" content="@(ViewData["OgTitle"] ?? ViewData["Title"])">
    <meta property="og:description" content="@(ViewData["OgDescription"] ?? ViewData["Description"])">
    @if (ViewData["OgImage"] != null)
    {
        <meta property="og:image" content="@ViewData["OgImage"]">
    }

    <!-- Twitter -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="@ViewData["Title"]">
    <meta name="twitter:description" content="@ViewData["Description"]">
</head>
<body>
    @RenderBody()
</body>
</html>
```

```cshtml theme={null}
@* Views/Page/Landing.cshtml *@
@model ButterCMS.Models.Page<LandingPageFields>

@{
    ViewData["Title"] = Model.Fields.Seo?.Title ?? Model.Fields.Headline;
    ViewData["Description"] = Model.Fields.Seo?.Description ?? "";
    ViewData["OgTitle"] = Model.Fields.Seo?.OgTitle ?? Model.Fields.Seo?.Title ?? Model.Fields.Headline;
    ViewData["OgDescription"] = Model.Fields.Seo?.OgDescription ?? Model.Fields.Seo?.Description ?? "";
    ViewData["OgImage"] = Model.Fields.Seo?.OgImage;
}

<main>
    <!-- Page content -->
</main>
```

## Resources

<CardGroup cols={2}>
  <Card title=".NET Starter" icon="rocket" href="../starter-projects/dotnet">
    Pre-configured starter project
  </Card>

  <Card title=".NET SDK" icon="microsoft" href="../sdks/dotnet-sdk">
    Complete SDK reference
  </Card>

  <Card title="GitHub Repository" icon="github" href="https://github.com/ButterCMS/buttercms-csharp">
    View source code
  </Card>

  <Card title="Webhooks" icon="bell" href="../../api/concepts/webhooks-overview">
    Set up content webhooks
  </Card>
</CardGroup>
