Exam Area: Area 3 – Website Implementation & Delivery (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/integrations
// Optimizely Content Delivery API
// NuGet: EPiServer.ContentDeliveryApi.Cms
builder.Services.AddContentDeliveryApi(options =>
{
options.EnablePreviewMode = true;
options.EnableAllProperties = false; // Only decorated properties
options.FlattenPropertyModel = false;
});
// Route: GET /api/episerver/v3.0/content/{contentLink}
// Route: GET /api/episerver/v3.0/content?contentUrl=/my-page
GET /api/episerver/v3.0/content → List content
GET /api/episerver/v3.0/content/{ref} → Get by reference
GET /api/episerver/v3.0/content/{ref}/children → Get children
GET /api/episerver/v3.0/search?query=... → Search
GET /api/episerver/v3.0/site → Site info
// Custom property expansion in API response
[ServiceConfiguration(typeof(IPropertyConverter))]
public class RatingPropertyConverter : IPropertyConverter
{
public ExpandedValue ConvertToExpanded(
IContent content,
PropertyData property,
ConverterContext context)
{
if (property is PropertyRating rating)
{
return new ExpandedValue
{
Value = rating.Value,
Maximum = rating.Maximum
};
}
return null;
}
}
// Model returned from the API
// Can be customized with IContentModelMapper
[ServiceConfiguration(typeof(IContentModelMapper))]
public class CustomContentModelMapper : IContentModelMapper
{
private readonly IContentModelMapper _defaultMapper;
public CustomContentModelMapper(DefaultContentModelMapper defaultMapper)
{
_defaultMapper = defaultMapper;
}
public ContentApiModel TransformContent(
IContent content,
bool excludePersonalizedContent,
string expand)
{
var model = _defaultMapper.TransformContent(content, excludePersonalizedContent, expand);
// Add custom data
if (content is ArticlePage article)
{
model.Properties["customField"] = article.CustomField;
}
return model;
}
}
// Use Content Events to trigger external integration
[InitializableModule]
public class ExternalIntegrationModule : IInitializableModule
{
private IContentEvents _events;
public void Initialize(InitializationEngine context)
{
_events = context.Locate.Advanced.GetInstance<IContentEvents>();
_events.PublishedContent += OnPublished;
}
public void Uninitialize(InitializationEngine context)
{
_events.PublishedContent -= OnPublished;
}
private async void OnPublished(object sender, ContentEventArgs e)
{
if (e.Content is ProductPage product)
{
// POST to external system
using var client = new HttpClient();
await client.PostAsJsonAsync(
"https://external.api/products/sync",
new { id = product.ContentLink.ID, name = product.Name });
}
}
}
// Send events to Azure Service Bus
builder.Services.AddAzureServiceBusEventProvider(options =>
{
options.ConnectionString = builder.Configuration["ServiceBus:ConnectionString"];
options.TopicName = "cms-events";
options.SubscriptionName = Environment.MachineName;
});
// IDataImporter / IDataExporter for bulk operations
public class ProductImportService
{
private readonly IContentRepository _repository;
private readonly IContentTypeRepository _contentTypeRepo;
public async Task ImportProductsFromCsvAsync(string csvPath)
{
var products = ReadCsv(csvPath);
var parentLink = new ContentReference(123); // Products container
foreach (var product in products)
{
var page = _repository.GetDefault<ProductPage>(parentLink);
page.Name = product.Name;
page.Price = product.Price;
_repository.Save(page, SaveAction.Publish);
}
}
}
// Use Scheduled Job for periodic imports
[ScheduledPlugIn(
DisplayName = "Import Products from ERP",
GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
DefaultIntervalType = ScheduledIntervalType.Hours,
DefaultInterval = 4)]
public class ProductImportJob : ScheduledJobBase
{
private readonly ProductImportService _importService;
public ProductImportJob(ProductImportService importService)
{
_importService = importService;
IsStoppable = false;
}
public override string Execute()
{
_importService.ImportFromERP();
return "Import completed successfully";
}
}
EPiServer.ContentDeliveryApi.Cms)/api/episerver/v3.0/)IContentEvents.PublishedContent)IContentModelMapper used for? (Customize the JSON output of the Content Delivery API)