HomeGuidesAPI Reference
Submit Documentation FeedbackJoin Developer CommunityOptimizely GitHubOptimizely NuGetLog In

Customize Content Delivery API for edit view

Describes how to configure edit view for displaying content in the Optimizely Content Delivery API.

Return draft and expired content

From Optimizely Content Delivery API 2.6.0, draft and expired content is not returned by Content Delivery API by default. If you try to retrieve a draft/expired content, you will probably get a 404 Not Found. This is convenient when you use Content Delivery API to build a Single-Page Application (SPA) for view mode, and when the client should not care about the draft or expired page – as in a standard Alloy site. However, in edit view, you may want to get all content, including the draft and expired content. Content Delivery API allows you to easily customize its ContentLoaderService to achieve this.

First, create a new class CustomContentLoaderService derived from ContentLoaderService; and within this class, override the function ShouldContentBeExposed. Finally, register a new class as the default service for ContentLoaderService in initialization module.

Example:

public void ConfigureContainer(ServiceConfigurationContext context)
  {
    context.Services.AddSingleton<ContentLoaderService, CustomContentLoaderService>();
  }
public class CustomContentLoaderService : ContentLoaderService
  {
    private readonly ServiceAccessor<HttpContextBase> _httpContextAccessor;
    public CustomContentLoaderService(IContentLoader contentLoader, ServiceAccessor<HttpContextBase> httpContextAccessor) : base(contentLoader)
      {
        _httpContextAccessor = httpContextAccessor;
      }
    protected override bool ShouldContentBeExposed(IContent content)
      {
        //In EditMode, unpublished or expired content is still returned
        if (GetContextMode() == ContextMode.Edit)
          {
            return true;
          }
        return base.ShouldContentBeExposed(content);
      }
    private ContextMode GetContextMode()
      {
        var httpCtx = _httpContextAccessor();
        if (httpCtx == null || httpCtx.Request == null || httpCtx.Request.QueryString[PageEditing.EpiEditMode] == null)
          {
            return ContextMode.Default;
          }
        if (bool.TryParse(httpCtx.Request.QueryString[PageEditing.EpiEditMode], out bool editMode))
          {
            return editMode ? ContextMode.Edit : ContextMode.Preview;
          }
        return ContextMode.Undefined;
      }
  }

 Resolve URL based on context

When you fetch content from Content Delivery API, there is a URL property that contains the CMS friendly URL of the requested content (for example, /en/artists/). However, the content URL is different in edit view. If you want to get the right URL in edit view, like on the Alloy site, you should override the UrlResolverService to resolve the correct URL of the content, based on the current request context.

  • Create a new class called CustomUrlResolverService that derived from UrlResolverService, and override the ResolveUrl method.
  • Register this new class as the default service for UrlResolverService in InitializableModule.

Example (Content Delivery API 2.9.0 and higher)

public void ConfigureContainer(ServiceConfigurationContext context)
  {
    context.Services.AddSingleton<UrlResolverService, CustomUrlResolverService>();
  }
public class CustomUrlResolverService : UrlResolverService
  {
    private readonly ServiceAccessor<HttpContextBase> _httpContextAccessor;
    public CustomUrlResolverService(UrlResolver urlResolver,
           ContentApiConfiguration contentApiConfiguration,
           ServiceAccessor<HttpContextBase> httpContextAccessor) : base(urlResolver, contentApiConfiguration)
      {
        _httpContextAccessor = httpContextAccessor;
      }
    public override string ResolveUrl(ContentReference contentLink, string language)
      {
        return _urlResolver.GetUrl(contentLink, language, new VirtualPathArguments
          {
             ContextMode = GetContextMode()
          });
      }
    private ContextMode GetContextMode()
      {
        var httpCtx = _httpContextAccessor();
        if (httpCtx == null || httpCtx.Request == null || httpCtx.Request.QueryString[PageEditing.EpiEditMode] == null)
          {
            return ContextMode.Default;
          }
        if (bool.TryParse(httpCtx.Request.QueryString[PageEditing.EpiEditMode], out bool editMode))
          {
            return editMode ? ContextMode.Edit : ContextMode.Preview;
          }
        return ContextMode.Undefined;
      }
}

Example (Content Delivery API 2.6.0):

public void ConfigureContainer(ServiceConfigurationContext context)
  {
    context.Services.AddSingleton<UrlResolverService, CustomUrlResolverService>();
  }
public class CustomUrlResolverService : UrlResolverService
  {
    private readonly ServiceAccessor<HttpContextBase> _httpContextAccessor;
    public CustomUrlResolverService(IUrlResolver urlResolver, ServiceAccessor<HttpContextBase> httpContextAccessor) : base(urlResolver)
      {
        _httpContextAccessor = httpContextAccessor;
      }
    public override string ResolveUrl(ContentReference contentLink, string language)
      {
        return _urlResolver.GetUrl(contentLink, language, new UrlResolverArguments
          {
            ContextMode = GetContextMode()
          });
      }
    private ContextMode GetContextMode()
      {
        var httpCtx = _httpContextAccessor();
        if (httpCtx == null || httpCtx.Request == null || httpCtx.Request.QueryString[PageEditing.EpiEditMode] == null)
          {
            return ContextMode.Default;
          }
        if (bool.TryParse(httpCtx.Request.QueryString[PageEditing.EpiEditMode], out bool editMode))
          {
            return editMode ? ContextMode.Edit : ContextMode.Preview;
          }
        return ContextMode.Undefined;
      }
  }

 Customize visitor group

By default, Content Delivery API filters content based on the visitor group of the current user. You may want to send a visitor group ID in request parameters and use this ID to filter content. A use case is, for example, the preview in CMS; you can change the visitor group ID in the drop-down menu and the page is displayed as viewed by the selected visitor group.

1210

When using Content Delivery API to build a SPA edit view, you can customize the ContentModelMapperBase to make Content Delivery API filter content based on the visitor group retrieved from request context.

  • First, create a new class called CustomContentModelMapper derived from ContentModelMapperBase and mark it with [ServiceConfiguration(typeof(IContentModelMapper))].
  • Override the abstract Order property and set it value to 200.  The default model mapper Order is 100, so a higher value is needed to priotritize the custom model mapper.
  • Override the abstract method CanHandle.
  • Override the TransformContent method.
[ServiceConfiguration(typeof(IContentModelMapper))]
public class CustomContentModelMapper : ContentModelMapperBase
  {
    private readonly ServiceAccessor<HttpContextBase> _httpContextAccessor;
    public CustomContentModelMapper(IContentTypeRepository contentTypeRepository,
                                    ReflectionService reflectionService,
                                    IContentModelReferenceConverter contentModelService, 
                                    IEnumerable<IPropertyModelConverter> propertyModelConverters, 
                                    IContentVersionRepository contentVersionRepository, 
                                    ContentLoaderService contentLoaderService, 
                                    UrlResolverService urlResolverService, 
                                    ServiceAccessor<HttpContextBase> httpContextAccessor) 
                                    : base(contentTypeRepository,
                                           reflectionService,
                                           contentModelService,
                                           propertyModelConverters,
                                           contentVersionRepository,
                                           contentLoaderService,
                                           urlResolverService)
      {
        _httpContextAccessor = httpContextAccessor;
      }
    public override int Order
      {
        get
          {
            return 200;
          }
      }
    /// <summary>
    /// Maps an instance of IContent to ContentApiModel and additionally add info about existing languages
    /// </summary>
    public override ContentApiModel TransformContent(IContent content, bool excludePersonalizedContent, string expand)
      {
        var visitorGroupId = HttpContext.Current.Request.QueryString[VisitorGroupHelpers.VisitorGroupKeyByID];
        if (!string.IsNullOrEmpty(visitorGroupId))
          {
            // setup impersonate visitor group
            var httpContextBase = new HttpContextWrapper(HttpContext.Current);
            httpContextBase.SetupVisitorGroupImpersonation(content, AccessLevel.Read);
          }
        var contentModel = base.TransformContent(content, excludePersonalizedContent, expand);
        return contentModel;
      }
    public override bool CanHandle<T>(T content)
      {
        // NOTE: you can uncomment the below code to make this custom mapper only active when in Edit Mode
        //var contextMode = GetContextMode();
        //return (contextMode == ContextMode.Edit || contextMode == ContextMode.Preview);
        return content is IContent;
      }
    private ContextMode GetContextMode()
      {
        var httpCtx = _httpContextAccessor();
        if (httpCtx == null || httpCtx.Request == null || httpCtx.Request.QueryString[PageEditing.EpiEditMode] == null)
          {
            return ContextMode.Default;
          }
        if (bool.TryParse(httpCtx.Request.QueryString[PageEditing.EpiEditMode], out bool editMode))
          {
            return editMode ? ContextMode.Edit : ContextMode.Preview;
          }
        return ContextMode.Undefined;
      }
  }