What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
What is your developer-dependent CMS actually costing you? Calculate your costs and get a full report
Tutorials
This guide helps you migrate from older Agility CMS .NET implementations to the modern Blazor or MVC starters.
This guide helps you migrate from older Agility CMS .NET implementations to the modern Blazor or MVC starters.
The new .NET starters represent a significant modernization of Agility CMS integration:
| Aspect | Legacy (.NET MVC 4/5) | Modern (.NET 10) |
|---|---|---|
| Content Access | Content Repository | Fetch API |
| Page Management | global.asax routes | Middleware + DI |
| URL Redirects | Built-in, required | Opt-in middleware |
| Caching | Automatic | Configurable |
| Content Types | Manual mapping | Typed endpoints |
| API | REST only | REST + GraphQL |
Identify what you're using from the legacy SDK:
Items(), GetById(), GetItemsByIDs()global.asaxThe Blazor and MVC starters target .NET 10, the current long-term support (LTS) release, so install the .NET 10 SDK first. .NET 8 and 9 reach end of support on November 10, 2026, so migrate straight to .NET 10 rather than to an older version.
git clone https://github.com/agility/agilitycms-dotnet-starter.git
cd agilitycms-dotnet-starter/Agility.NET.Blazor.Starter
# Add appsettings.local.json with your keys
dotnet watch
Your biggest task is converting views/components. The content models and logic remain similar.
// Legacy approach
public class BlogController : Controller
{
public ActionResult Index()
{
var repository = new ContentRepository();
var posts = repository.Items<Post>("posts")
.Where(p => p.Status == "published")
.OrderByDescending(p => p.Date)
.Take(10)
.ToList();
return View(posts);
}
public ActionResult Detail(int id)
{
var repository = new ContentRepository();
var post = repository.GetById<Post>(id);
return View(post);
}
}
// Modern approach - Blazor
@inject FetchApiService FetchApi
@code {
private List<Post>? posts;
protected override async Task OnInitializedAsync()
{
posts = await FetchApi.GetTypedContentList<Post>(
referenceName: "posts",
locale: "en-us",
take: 10,
sort: "fields.date",
direction: "desc"
);
}
}
// Modern approach - MVC
public class BlogController : Controller
{
private readonly FetchApiService _fetchApi;
public BlogController(FetchApiService fetchApi)
{
_fetchApi = fetchApi;
}
public async Task<IActionResult> Index()
{
var posts = await _fetchApi.GetTypedContentList<Post>(
referenceName: "posts",
locale: "en-us",
take: 10,
sort: "fields.date",
direction: "desc"
);
return View(posts);
}
}
| Legacy | Modern |
|---|---|
repository.Items<T>("reference") | fetchApi.GetTypedContentList<T>("reference", locale) |
repository.GetById<T>(id) | fetchApi.GetTypedContentItem<T>(id, locale) |
| LINQ filtering | API filter parameter |
| Synchronous | Async/await |
// Global.asax.cs
protected void Application_Start()
{
// Agility route registration
AgilityRouteConfig.RegisterRoutes(RouteTable.Routes);
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddSingleton<FetchApiService>(...);
builder.Services.AddSingleton<AgilityRouteTransformer>();
var app = builder.Build();
// Configure middleware
app.UseStaticFiles();
app.UseRouting();
app.UseAgilityRedirects(); // Optional: URL redirects
// Dynamic page routing
app.MapDynamicPageRoute<AgilityRouteTransformer>("{**slug}");
DynamicRouteValueTransformerURL redirects were always enabled with no customization.
// Program.cs - Add only if you need redirects
app.UseAgilityRedirects();
Or create custom redirect logic:
// Custom redirect middleware
public class CustomRedirectMiddleware
{
private readonly RequestDelegate _next;
private readonly FetchApiService _fetchApi;
public async Task InvokeAsync(HttpContext context)
{
var redirects = await _fetchApi.GetUrlRedirects("en-us");
var match = redirects.FirstOrDefault(r =>
r.OriginUrl.Equals(context.Request.Path, StringComparison.OrdinalIgnoreCase));
if (match != null)
{
// Custom logic here
context.Response.Redirect(match.DestinationUrl, permanent: true);
return;
}
await _next(context);
}
}
// Models/Post.cs
public class Post
{
public int ContentID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime Date { get; set; }
public string Slug { get; set; }
}
// Models/AgilityModels.cs
public class Post
{
public string? Title { get; set; }
public string? Slug { get; set; }
public DateTime? Date { get; set; }
public string? Excerpt { get; set; }
public string? Content { get; set; }
public ImageAttachment? Image { get; set; }
public ContentReference? Category { get; set; }
}
public class ImageAttachment
{
public string? Url { get; set; }
public string? Label { get; set; }
public int? Width { get; set; }
public int? Height { get; set; }
}
There is no Agility tool that generates C# model classes, so write them by hand as shown above. The Agility CLI (@agility/cli on npm) copies content and models between instances; it doesn't generate code.
If you have many models, you can write your own generator: read the model definitions with the .NET Management SDK (client.Models.GetContentModelsAsync) and write out a class for each model.
Don't install a .NET tool named
agility-cli. Earlier versions of this guide showeddotnet tool install -g agility-cli. No package with that name is published on NuGet, and anyone could register it, so that command could install code Agility doesn't control.
@* Views/Blog/Index.cshtml *@
@model IEnumerable<Post>
<div class="posts">
@foreach (var post in Model)
{
<article>
<h2><a href="/blog/@post.Slug">@post.Title</a></h2>
<time>@post.Date.ToString("MMMM d, yyyy")</time>
@Html.Raw(post.Excerpt)
</article>
}
</div>
@* Components/AgilityComponents/PostsListing.razor *@
<div class="posts grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@if (posts != null)
{
@foreach (var post in posts)
{
<article class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold mb-2">
<a href="/blog/@post.Slug" class="hover:text-primary-500">
@post.Title
</a>
</h2>
<time class="text-gray-500 text-sm">
@post.Date?.ToString("MMMM d, yyyy")
</time>
<div class="mt-4 prose">
@((MarkupString)(post.Excerpt ?? ""))
</div>
</article>
}
}
</div>
@code {
private List<Post>? posts;
protected override async Task OnInitializedAsync()
{
posts = await AgilityService.GetPostsAsync("en-us");
}
}
// ViewComponents/PageModules/PostsListing.cs
public class PostsListing : ViewComponent
{
private readonly FetchApiService _fetchApi;
public PostsListing(FetchApiService fetchApi)
{
_fetchApi = fetchApi;
}
public async Task<IViewComponentResult> InvokeAsync(ModuleModel moduleModel)
{
var posts = await _fetchApi.GetTypedContentList<Post>(
"posts", moduleModel.Locale, take: 10);
return View("/Views/PageModules/PostsListing.cshtml", posts);
}
}
@* Views/PageModules/PostsListing.cshtml *@
@model List<Post>
<div class="posts grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach (var post in Model)
{
<article class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold mb-2">
<a href="/blog/@post.Slug">@post.Title</a>
</h2>
<time class="text-gray-500 text-sm">
@post.Date?.ToString("MMMM d, yyyy")
</time>
<div class="mt-4 prose">
@Html.Raw(post.Excerpt)
</div>
</article>
}
</div>
| Legacy Method | Modern Method |
|---|---|
repository.Items<T>(ref) | GetTypedContentList<T>(ref, locale) |
repository.GetById<T>(id) | GetTypedContentItem<T>(id, locale) |
repository.GetItemsByIDs<T>(ids) | Loop with GetTypedContentItem<T> or GraphQL |
| N/A | GetContentByGraphQL<T>(query, objName, locale) |
| N/A | GetTypedPage(pageId, locale) |
| N/A | GetSitemapFlat(channel, locale) |
| N/A | GetUrlRedirects(locale) |
<appSettings>
<add key="Agility.ContentAccessor.InstanceGuid" value="..." />
<add key="Agility.ContentAccessor.ApiKey" value="..." />
</appSettings>
{
"AppSettings": {
"InstanceGUID": "your-guid",
"SecurityKey": "your-key",
"FetchAPIKey": "defaultlive.your-key",
"PreviewAPIKey": "defaultpreview.your-key",
"Locales": "en-us",
"ChannelName": "website",
"CacheInMinutes": 5
}
}
GetTypedContentList<T>, GetTypedPage, etc.appsettings.local.jsonModels/AgilityModels.csIf you encounter issues during migration: