Performance considerations: Implement caching and minimize external API calls in routing logic.
Security and governance: Sanitize HTML and enforce consistent editorial workflows.
Headless support: External content is accessible via the Content Delivery API for SPA or mobile apps.
1. Why Integrate External Content?
Content Centralization: Consolidate product catalogs, news feeds, or other external sources under a unified CMS editorial experience.
Data Reusability: Maintain a single source of truth and avoid content duplication.
Decoupled Architectures: Supports microservices and headless CMS patterns where Optimizely acts as the presentation layer.
2. The Role of IContentProvider
IContentProvider serves as the bridge between external content and Optimizely CMS, making external items manageable in the editorial interface as if they were native content. Developers inherit from ContentProvider or ContentProviderBase and implement methods to fetch, transform, and expose external content.
Step 1 - Define Content Models
Map external content structure to ContentData types.
C#
[ContentType(DisplayName = "ExternalArticle", GUID = "...", AvailableInEditMode = false)]
public class ExternalArticlePage : PageData
{
[CultureSpecific]
[Display(Name = "External Title", Order = 10)]
public virtual string ExternalTitle { get; set; }
[CultureSpecific]
[Display(Name = "External Body", Order = 20)]
public virtual XhtmlString ExternalBody { get; set; }
public virtual string ExternalId { get; set; }
public virtual string ExternalUrl { get; set; }
}
Step 2 - Implement ContentProvider Logic
Fetch external data and map it to CMS content instances.
C#
[ContentProvider]
public class ExternalContentProvider : ContentProvider
{
private readonly ExternalApiService _externalApiService;
public ExternalContentProvider(ExternalApiService externalApiService)
{
_externalApiService = externalApiService;
}
public override ContentReference GetContentLink(Uri url)
{
// Map external URL segments to ContentReference
if (url.Segments.Length > 2 &&
url.Segments[1].Trim('/').Equals("external-articles", StringComparison.OrdinalIgnoreCase))
{
var externalId = url.Segments[2].Trim('/');
return new ContentReference(0, externalId, ProviderKey);
}
return ContentReference.EmptyReference;
}
protected override IContent LoadContent(ContentReference contentLink, LanguageSelector selector)
{
if (contentLink.ProviderValue != null)
{
var externalId = contentLink.ProviderValue.ToString();
var externalData = _externalApiService.GetArticleById(externalId);
if (externalData != null)
{
var page = CreateContent<ExternalArticlePage>(contentLink);
page.ExternalTitle = externalData.Title;
page.ExternalBody = new XhtmlString(externalData.BodyHtml);
page.ExternalId = externalData.Id;
page.ExternalUrl = externalData.Url;
page.Name = externalData.Title;
page.StartPublish = DateTime.MinValue;
page.StopPublish = DateTime.MaxValue;
return page;
}
}
return null;
}
}
Step 3 - Register the ContentProvider
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddCms();
services.AddSingleton<ExternalApiService>();
services.AddContentProvider<ExternalContentProvider>();
}
3. The Role of IPartialRouter
IPartialRouter maps URLs to external content items, enabling SEO-friendly URLs that do not follow the internal CMS hierarchy.
Custom PartialRouter Implementation
C#
public class ExternalArticlePartialRouter : IPartialRouter<PageData, ExternalArticlePage>
{
private readonly ExternalApiService _externalApiService;
public ExternalArticlePartialRouter(ExternalApiService externalApiService)
{
_externalApiService = externalApiService;
}
public PartialRouteData Get(PageData content, SegmentContext segmentContext)
{
var nextSegment = segmentContext.RemainingPath;
if (string.IsNullOrEmpty(nextSegment)) return null;
var parts = nextSegment.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 1) return null;
var externalId = parts[0];
var externalArticle = _externalApiService.GetArticleById(externalId);
if (externalArticle != null)
{
var contentLink = new ContentReference(0, externalArticle.Id, "ExternalContentProviderKey");
var externalPage = contentLink.Get<ExternalArticlePage>();
if (externalPage != null)
{
segmentContext.RemainingPath = (parts.Length > 1)
? string.Join("/", parts.Skip(1))
: string.Empty;
return new PartialRouteData
{
ContentLink = externalPage.ContentLink,
RouteValues = new Dictionary<string, object> { { "externalId", externalId } }
};
}
}
return null;
}
public EntryPoint Parse(ContentReference contentLink, SegmentContext segmentContext) => null;
public bool RoutePartial(PageData content, Framework.Web.Routing.SegmentContext segmentContext) => false;
}
Register the PartialRouter
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddCms();
services.AddSingleton<ExternalApiService>();
services.AddContentProvider<ExternalContentProvider>();
services.AddTransient<ExternalArticlePartialRouter>();
services.AddSingleton<IRegisterPartialRoutes>(ctx =>
{
var router = ctx.GetService<ExternalArticlePartialRouter>();
return new RegisterPartialRoutes<ContainerPage>(router);
});
}
Note: Implement ContentProvider and PartialRouter together to ensure reliable, consistent URL mappings between external content and the CMS.
4. Rendering External Content in Templates
Controller
C#
public class ExternalArticlePageController : PageController<ExternalArticlePage>
{
public ActionResult Index(ExternalArticlePage currentPage)
{
var model = new ExternalArticlePageViewModel
{
Title = currentPage.ExternalTitle,
Body = currentPage.ExternalBody.ToHtmlString(),
ExternalSourceUrl = currentPage.ExternalUrl
};
return View(model);
}
}
Important: Always sanitize HTML received from external sources before passing it to Html.Raw(). Rendering unsanitized external HTML is a significant XSS risk.
5. Best Practices and Considerations
Implementation guidance
Select a topic to expand and read the details.
Performance and Caching▼
Use IMemoryCache or IDistributedCache at the ContentProvider level alongside CDN caching.
Cache external API calls to avoid routing bottlenecks under load.
Minimize the number of external API calls made within routing logic paths.
Error Handling and Resilience▼
Provide fallback content and handle API failures gracefully.
Return null from LoadContent rather than throwing, to avoid breaking the CMS rendering pipeline.
Security▼
Sanitize all HTML and scripts received from external sources before storing or rendering.
Never pass raw external HTML directly to Html.Raw() without sanitization.
UI Integration and Headless Support▼
Ensure external content is coherent with the editorial UI using DisplayName, UIHint, or custom editors.
Expose external content through the Content Delivery API for SPAs or mobile apps.
Pro tip: Implement ContentProvider and PartialRouter in tandem from the start. Adding URL routing as an afterthought often requires content data migrations to correct mismatched references.
Conclusion
Using IContentProvider and IPartialRouter, external content can be treated as a first-class citizen within Optimizely CMS 12. By combining thoughtful implementation, caching strategies, security practices, and routing logic, developers can deliver a seamless editorial and end-user experience while maintaining flexibility and performance on PaaS.