# How Page Models Work in .NET

> Source: https://agilitycms.com/docs/dotNet/how-page-models-work

Page Models define the structure and layout of pages in Agility CMS. They determine where editors can place components and how the page is visually organized.

## What is a Page Model?

A Page Model is a template that defines:

1. **Name** - A descriptive identifier (e.g., "Main Template", "Two Column Layout", "Blog Page")
2. **Zones** - Areas where editors can add, edit, and remove components

Think of Page Models as the skeleton of a page. They define the layout structure, while components fill in the actual content.

## Example Page Models

### Single Column Layout
```
┌─────────────────────────────┐
│        Header Zone          │
├─────────────────────────────┤
│                             │
│     Main Content Zone       │
│                             │
├─────────────────────────────┤
│        Footer Zone          │
└─────────────────────────────┘
```

### Two Column Layout
```
┌─────────────────────────────┐
│        Header Zone          │
├──────────────┬──────────────┤
│              │              │
│   Left Zone  │  Right Zone  │
│              │              │
├──────────────┴──────────────┤
│        Footer Zone          │
└─────────────────────────────┘
```

### Blog Post Layout
```
┌─────────────────────────────┐
│        Hero Zone            │
├─────────────────────────────┤
│     Article Content Zone    │
├─────────────────────────────┤
│     Related Posts Zone      │
└─────────────────────────────┘
```

## Creating Page Models in Agility CMS

1. Go to **Settings > Page Models** in Agility CMS
2. Click **New** to create a Page Model
3. Enter a **Name** (e.g., "Main Template")
4. Add **Zones** - these become the areas where editors place components

Each zone you create will be available in the code for rendering.

## Implementing Page Models in .NET

### Blazor Implementation

In Blazor, Page Models are implemented as layouts or conditionally rendered sections:

```razor
@* Components/Pages/AgilityPage.razor *@
@page "/{*slug}"

<MainLayout>
    @foreach (var zone in PageData.Zones)
    {
        <section class="zone zone-@zone.Key.ToLower().Replace(" ", "-")">
            @foreach (var module in zone.Value.Modules)
            {
                <AgilityComponent
                    ComponentName="@module.ModuleName"
                    Fields="@module.Fields"
                    CustomData="@module.CustomData" />
            }
        </section>
    }
</MainLayout>
```

For different layouts based on Page Model name:

```razor
@switch (PageData.TemplateName)
{
    case "Main Template":
        <MainLayout>
            @RenderZones()
        </MainLayout>
        break;

    case "Two Column Layout":
        <TwoColumnLayout LeftZone="@GetZone("LeftZone")"
                        RightZone="@GetZone("RightZone")" />
        break;

    case "Blog Page":
        <BlogLayout HeroZone="@GetZone("HeroZone")"
                   ContentZone="@GetZone("ContentZone")" />
        break;
}
```

### MVC Implementation

In MVC, Page Models map to Razor view files in the `Views/PageTemplates/` folder:

```
Views/
└── PageTemplates/
    ├── MainTemplate.cshtml
    ├── TwoColumnLayout.cshtml
    └── BlogPage.cshtml
```

The Page Model name is converted to a file path by removing spaces:

| Page Model Name | View File |
|-----------------|-----------|
| Main Template | `MainTemplate.cshtml` |
| Two Column Layout | `TwoColumnLayout.cshtml` |
| Blog Page | `BlogPage.cshtml` |

#### Main Template Example

```razor
@* Views/PageTemplates/MainTemplate.cshtml *@
@model AgilityPageModel

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    @await Component.InvokeAsync("SEO", Model)
    <link href="~/css/output.css" rel="stylesheet" />
</head>
<body>
    @await Component.InvokeAsync("SiteHeader")

    <main>
        @* Render the MainContentZone *@
        @await Html.RenderZoneAsync("MainContentZone")
    </main>

    @await Component.InvokeAsync("SiteFooter")
</body>
</html>
```

#### Two Column Layout Example

```razor
@* Views/PageTemplates/TwoColumnLayout.cshtml *@
@model AgilityPageModel

<!DOCTYPE html>
<html lang="en">
<head>
    @await Component.InvokeAsync("SEO", Model)
    <link href="~/css/output.css" rel="stylesheet" />
</head>
<body>
    @await Component.InvokeAsync("SiteHeader")

    <main class="container mx-auto">
        <div class="flex flex-wrap -mx-4">
            <div class="w-full lg:w-2/3 px-4">
                @await Html.RenderZoneAsync("LeftZone")
            </div>
            <aside class="w-full lg:w-1/3 px-4">
                @await Html.RenderZoneAsync("RightZone")
            </aside>
        </div>
    </main>

    @await Component.InvokeAsync("SiteFooter")
</body>
</html>
```

## The RenderZoneAsync Helper

Both starters include a helper for rendering zones. It:

1. Gets the zone from the page data
2. Iterates through each module in the zone
3. Invokes the appropriate component for each module

```csharp
// Simplified implementation
public static async Task RenderZoneAsync(this IHtmlHelper html, string zoneName)
{
    var page = html.ViewData["PageData"] as PageResponse;
    var zone = page?.Zones?.GetValueOrDefault(zoneName);

    if (zone?.Modules == null) return;

    foreach (var module in zone.Modules)
    {
        await html.RenderComponentAsync(module);
    }
}
```

## Dynamic Page Model Selection

The page rendering system automatically selects the correct Page Model based on the `TemplateName` property from Agility:

```csharp
// Get the template path
var templatePath = PageHelpers.GetPageTemplatePath(page.TemplateName);
// Returns: "/Views/PageTemplates/MainTemplate.cshtml"
```

```csharp
// PageHelpers.cs
public static string GetPageTemplatePath(string templateName)
{
    // Remove spaces from template name
    var fileName = templateName.Replace(" ", "");
    return $"/Views/PageTemplates/{fileName}.cshtml";
}
```

## Zone Data Structure

When you fetch a page, zones are returned as a dictionary:

```json
{
  "zones": {
    "MainContentZone": {
      "modules": [
        {
          "moduleName": "RichTextArea",
          "fields": {
            "textBlob": "<p>Hello world</p>"
          }
        },
        {
          "moduleName": "TextBlockWithImage",
          "fields": {
            "title": "Our Services",
            "content": "<p>We offer...</p>",
            "image": { "url": "...", "label": "..." }
          }
        }
      ]
    },
    "SidebarZone": {
      "modules": [
        {
          "moduleName": "Newsletter",
          "fields": {
            "heading": "Subscribe",
            "buttonText": "Sign Up"
          }
        }
      ]
    }
  }
}
```

## Best Practices

### 1. Use Descriptive Zone Names

Choose zone names that clearly indicate their purpose:

| Good | Avoid |
|------|-------|
| MainContentZone | Zone1 |
| HeroSection | Top |
| SidebarWidgets | Right |
| FooterLinks | Bottom |

### 2. Keep Page Models Flexible

Design Page Models that work for multiple page types. A "Main Template" with a single content zone is more flexible than many specialized templates.

### 3. Document Zone Purposes

Add comments or documentation so editors know what types of components work best in each zone:

```razor
@* MainContentZone: Full-width content modules *@
@await Html.RenderZoneAsync("MainContentZone")

@* SidebarZone: Narrow widgets, CTAs, navigation *@
@await Html.RenderZoneAsync("SidebarZone")
```

### 4. Handle Missing Zones Gracefully

Not all pages use all zones. Check for null zones:

```razor
@if (PageData.Zones.ContainsKey("SidebarZone"))
{
    <aside>
        @await Html.RenderZoneAsync("SidebarZone")
    </aside>
}
```

### 5. Consider Mobile Layouts

Design zones that work well on all screen sizes:

```razor
<div class="flex flex-col lg:flex-row">
    <main class="w-full lg:w-2/3">
        @await Html.RenderZoneAsync("MainContentZone")
    </main>
    <aside class="w-full lg:w-1/3 mt-8 lg:mt-0">
        @await Html.RenderZoneAsync("SidebarZone")
    </aside>
</div>
```

## Shared Components

Some components appear on every page regardless of Page Model (like headers and footers). These are typically:

1. **Hard-coded** in the Page Model template
2. **Fetched** from a shared content item in Agility

```razor
@* Always include header and footer *@
@await Component.InvokeAsync("SiteHeader")

<main>
    @await Html.RenderZoneAsync("MainContentZone")
</main>

@await Component.InvokeAsync("SiteFooter")
```

## Next Steps

- [How Components Work](/docs/dotNet/how-components-work) - Build the components that fill zones
- [How Pages Work](/docs/dotNet/how-pages-work) - Understand page routing
- [Configuration](/docs/dotNet/configuration) - Set up caching and locales
