# How Pages Work in .NET

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

One of Agility CMS's core principles is that **editors should have full control over the pages on their website**. They shouldn't need a developer to create new pages, change URLs, or reorganize content.

This guide explains how .NET applications integrate with Agility's page management system to enable this editor-driven experience.

## The Sitemap

Every Agility CMS instance has a **sitemap** that defines:

- Which pages exist on the site
- The URL path for each page
- Which Page Model (layout) each page uses
- Which components appear on each page

Editors manage the sitemap directly in Agility CMS. Your .NET application reads this sitemap and renders pages accordingly.

### Example Sitemap

A typical blog site might have this sitemap:

| Page | URL | Page Model |
|------|-----|------------|
| Home | `/` | Main Template |
| Blog | `/blog` | Main Template |
| Blog Posts | `/blog/{slug}` | Main Template |
| About | `/about` | Main Template |

The "Blog Posts" page is a **dynamic page** - it generates individual URLs for each blog post (like `/blog/my-first-post`).

## Dynamic Page Routing

Both starters use a catch-all route that intercepts all incoming requests and matches them against the Agility sitemap.

### Blazor Implementation

```razor
@* Components/Pages/AgilityPage.razor *@
@page "/{*slug}"

@code {
    [Parameter] public string? Slug { get; set; }

    protected override async Task OnInitializedAsync()
    {
        // Get the sitemap
        var sitemap = await AgilityService.GetSitemapPagesAsync();

        // Find the page matching this URL
        var page = sitemap.FirstOrDefault(p =>
            p.Path.Equals($"/{Slug ?? ""}", StringComparison.OrdinalIgnoreCase));

        // Fetch full page data
        PageData = await AgilityService.GetPageAsync(page.PageID, locale);
    }
}
```

### MVC Implementation

The MVC starter uses a `DynamicRouteValueTransformer` for routing:

```csharp
// Middleware/AgilityRouteTransformer.cs
public class AgilityRouteTransformer : DynamicRouteValueTransformer
{
    public override async ValueTask<RouteValueDictionary> TransformAsync(
        HttpContext httpContext, RouteValueDictionary values)
    {
        var path = httpContext.Request.Path.Value ?? "/";

        // Get sitemap pages (cached)
        var sitemapPages = await GetSitemapPages();

        // Find matching page
        var page = sitemapPages.FirstOrDefault(p =>
            p.Path.Equals(path, StringComparison.OrdinalIgnoreCase));

        if (page != null)
        {
            values["page"] = "/AgilityPage";
            values["agilityPage"] = page;
        }

        return values;
    }
}
```

## Page Data Structure

When you fetch a page from Agility, you receive a structured response containing:

```csharp
public class PageResponse
{
    public int PageID { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
    public string TemplateName { get; set; }      // The Page Model name
    public Dictionary<string, Zone> Zones { get; set; }  // Content zones
    public SEO Seo { get; set; }                  // SEO metadata
}

public class Zone
{
    public string Name { get; set; }
    public List<Module> Modules { get; set; }     // Components in this zone
}

public class Module
{
    public string ModuleName { get; set; }        // Component type
    public dynamic Fields { get; set; }           // Component content
}
```

## Rendering a Page

The page rendering flow is:

1. **Match URL to sitemap** - Find the page definition
2. **Fetch page data** - Get zones and modules from Agility API
3. **Select Page Model** - Choose the layout based on `TemplateName`
4. **Render zones** - Iterate through each zone in the page
5. **Render components** - For each module in a zone, render the matching component

### Blazor Example

```razor
@* AgilityPage.razor *@
@foreach (var zone in PageData.Zones)
{
    <div class="zone zone-@zone.Key.ToLower()">
        @foreach (var module in zone.Value.Modules)
        {
            <AgilityComponent
                ComponentName="@module.ModuleName"
                Fields="@module.Fields" />
        }
    </div>
}
```

### MVC Example

```razor
@* Views/PageTemplates/MainTemplate.cshtml *@
@await Html.RenderZoneAsync("MainContentZone")
```

The `RenderZoneAsync` helper iterates through modules and invokes the appropriate ViewComponent for each one.

## Dynamic Pages

Dynamic pages generate URLs from content items. For example, a "Blog Posts" dynamic page creates individual pages for each blog post.

### How Dynamic Pages Work

1. In Agility CMS, create a dynamic page linked to a content list (e.g., "Posts")
2. Set the URL pattern (e.g., `/blog/{slug}`)
3. The sitemap includes entries for each content item:
   - `/blog/my-first-post`
   - `/blog/announcing-new-features`
   - `/blog/tips-and-tricks`

### Accessing Dynamic Page Content

When rendering a dynamic page, you have access to the linked content item:

```csharp
// The dynamic page includes the content item
var post = page.DynamicPageItem;  // The specific blog post

// Use it in your component
<h1>@post.Fields.Title</h1>
<div>@((MarkupString)post.Fields.Content)</div>
```

## Caching the Sitemap

For performance, both starters cache the sitemap in memory:

```csharp
// Sitemap is cached to avoid API calls on every request
var sitemap = await _cache.GetOrCreateAsync("sitemap", async entry =>
{
    entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
    return await _fetchApi.GetSitemapFlat(channelName, locale);
});
```

The cache is invalidated via webhooks when content is published in Agility CMS.

## Preview Mode

In preview mode, the application fetches draft content instead of published content. This allows editors to preview changes before publishing.

Preview mode is enabled:
- **Automatically** in development (`ASPNETCORE_ENVIRONMENT=Development`)
- **Via query parameter** in production (`?agilitypreviewkey=...`)

When preview mode is active:
- Draft/staging content is fetched
- Caching is disabled
- A preview indicator bar is shown

## URL Redirects

Agility CMS supports URL redirects managed by editors. Both starters include middleware to handle these redirects:

```csharp
// Middleware/AgilityRedirectMiddleware.cs
public async Task InvokeAsync(HttpContext context)
{
    var redirects = await GetRedirects();
    var redirect = redirects.FirstOrDefault(r =>
        r.OriginUrl.Equals(context.Request.Path, StringComparison.OrdinalIgnoreCase));

    if (redirect != null)
    {
        context.Response.Redirect(redirect.DestinationUrl, permanent: true);
        return;
    }

    await _next(context);
}
```

## Multi-Language Support

Agility CMS supports multi-language sites. Each locale has its own sitemap with localized content.

Configure supported locales in `appsettings.json`:

```json
{
  "AppSettings": {
    "Locales": "en-us,fr-ca,es-mx"
  }
}
```

The starters automatically detect the locale from the URL or use the default locale.

## Next Steps

- [How Components Work](/docs/dotNet/how-components-work) - Build the modules that appear on pages
- [How Page Models Work](/docs/dotNet/how-page-models-work) - Define page layouts and zones
- [Fetching Content](/docs/dotNet/fetching-content) - Query content beyond page data
