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.
Implementing a Custom ContentProvider
Developers inherit from ContentProvider or ContentProviderBase and implement methods to fetch, transform, and expose external content.
1. Define Content Models
Map external content structure to ContentData types.
[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; }
}
2. Implement ContentProvider Logic
Fetch external data and map it to CMS content instances.
[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.Set
{
parentLink = contentLink.ParentLink,
contentGuid = Guid.NewGuid(),
status = VersionStatus.Published
};
page.StartPublish = DateTime.MinValue;
page.StopPublish = DateTime.MaxValue;
return page;
}
}
return null;
}
}
Register the ContentProvider
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
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
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);
});
}
4. Rendering External Content in Templates
Controller
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);
}
}
Caching: Use IMemoryCache or IDistributedCache at the ContentProvider level alongside CDN caching.
Error Handling: Provide fallback content and handle API failures gracefully.
UI Integration: Ensure external content is coherent with the editorial UI via DisplayName, UIHint, or custom editors.
URL Consistency: Implement ContentProvider and PartialRouter methods together for reliable URL mappings.
Security: Sanitize all HTML and scripts received from external sources.
Performance: Cache external API calls to avoid routing bottlenecks.
Headless Support: Expose external content through the Content Delivery API for SPAs or mobile apps.
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.