Contentprovider example for #EPiServer, Former PageProvider, Part one

Content Providers in EPiServer is a way of publishing external data as pages/blocks etc... but also a way of publishing content in several places on your web.

icon of user profile

Last year I upgraded our web from EPiServer CMS 6 to 7.19, and later on to 8. One big breaking change was the Page Provider changing to Content Provider. I had big problems finding providers working for us. Not even the ClonedContentProvider from Alloy did work for us. Thats why I publish our solution of a ClonedContentProvider. The major difference in our version is that we don’t use GetDescendentReferences and ResolveContent, instead implementing ListMatchingSegments.

Content Providers in EPiServer is a way of publishing external data as pages/blocks etc… but also a way of publishing content in several places on your web. Due to the lack of publishing in several containers/parents, this is a way of cloning data and keeping friendly urls.

Registering a provider in webconfig / episerver

<episerver> <contentProvider> <providers> <add entryPoint="66" name="ClonedNews" sourceid="20" categoryid="16" type="Gosso.EPiServer.ContentProviders.ClonedByCategoryContentProvider, GOSSO.EPiServer" /> </providers> </contentProvider> </episerver>

To not filter by Category, use -1.

using EPiServer;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Web.Routing;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.Linq;

namespace Gosso.EPiStuff.ContentProviders

     * This replicates pages from one container to another
    public class ClonedByCategoryContentProvider : EPiServer.Core.ContentProvider

        private int CATEGORYID = -1;
        private int _sourceid = -1;
        private const string languageBranch = "en";
        private readonly IdentityMappingService _identityMappingService;
        private readonly IContentTypeRepository _contentTypeRepository;
        private readonly IContentFactory _contentFactory;
        private readonly ServiceLocationHelper locator;
        private readonly IUrlSegmentGenerator _urlSegment;
        private readonly CategoryRepository categoryRepository;        
        private readonly IContentCacheKeyCreator _cacheCreator;

        public ClonedCategoryContentProvider(IdentityMappingService identityMappingService,
            IContentTypeRepository _ContentTypeRepository,
            IContentFactory _ContentFactory,
            ServiceLocationHelper _Locator,
            IUrlSegmentGenerator _UrlSegment, 
            CategoryRepository _categoryRepository,
            IContentCacheKeyCreator _CacheCreator)
            _identityMappingService = identityMappingService;
            _contentTypeRepository = _ContentTypeRepository;
            _contentFactory = _ContentFactory;
            locator = _Locator;
            _urlSegment = _UrlSegment;
            categoryRepository = _categoryRepository;
            _cacheCreator = _CacheCreator;

        /// <summary>
        /// Initializes the provider from configuration settings.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="configParams">The config params.</param>
        public override void Initialize(string key, NameValueCollection configParams)
            //Let base classes do their initialization
            base.Initialize(key, configParams);

            if (string.IsNullOrEmpty(configParams["sourceid"]))
                throw new ConfigurationErrorsException("ClonedCategoryContentProvider requires configuration attribute sourceid");
            int id;
           if (int.TryParse(configParams["sourceid"],out id))
            { _sourceid = id; }

            if (!string.IsNullOrEmpty(configParams["categoryid"]))
                 int catid;
                if (int.TryParse(configParams["categoryid"], out catid))
                { CATEGORYID = catid; }

        protected override IContent LoadContent(ContentReference contentLink, ILanguageSelector languageSelector)
            MappedIdentity mappedIdentity = _identityMappingService.Get(contentLink);

            if (mappedIdentity != null && mappedIdentity.ExternalIdentifier != null)
                // SOMETIME PARENT IS CALLED FIRST!! breaking change CMS 8 : Possible to set EntryPoint for content provider that have exactly 1 child 
                string strId = mappedIdentity.ExternalIdentifier.Segments[1];
                int intId;
                var success = int.TryParse(strId, out intId);
                if (success)
                    var reference = new PageReference(intId);
                    PageData page = DataFactory.Instance.GetPage(reference, LanguageSelector.Fallback(languageBranch, true));

                    if (page != null)
                        return  CreateMirroredContent(mappedIdentity, page);
            return null;

        protected override IList<GetChildrenReferenceResult> LoadChildrenReferencesAndTypes(ContentReference contentLink, string languageID, out bool languageSpecific)
            languageSpecific = false;
            // Only the root node has children
            if (contentLink == this.EntryPoint)

                PageReference pr = new PageReference(_sourceid);
                var entryPoint = repository.Get<PageData>(this.EntryPoint);

                if (!PageReference.IsNullOrEmpty(pr))
                    System.Collections.Generic.IList<GetChildrenReferenceResult> list = new List<GetChildrenReferenceResult>();
                    var pages = DataFactory.Instance.GetChildren(pr, new LanguageSelector(entryPoint.LanguageBranch)).OrderByDescending(z => z.StartPublish).Where(s => s.CheckPublishedStatus(PagePublishedStatus.Published)); //.Where(x => x.Categories.IsEmpty);
                    foreach (PageData page in pages)
                         if (page.Category.MemberOf(CATEGORYID) || CATEGORYID<1)
                             Uri externalID = MappedIdentity.ConstructExternalIdentifier(ProviderKey, page.ContentLink.ToString());
                             var mappedIdentity = _identityMappingService.Get(externalID, true);

                             System.Type modelType = null;

                             modelType = typeof(PageData);

                             ContentReference contentRef = page.PageLink;
                             GetChildrenReferenceResult result = new GetChildrenReferenceResult
                                 ContentLink = mappedIdentity.ContentLink,
                                 ModelType = modelType, 

                    return list.Reverse().ToList();
            return null;


        /// <summary>
        /// Used for routing
        /// </summary>
        /// <param name="parentLink"></param>
        /// <param name="urlSegment"></param>
        /// <returns></returns>
        protected override IList<MatchingSegmentResult> ListMatchingSegments(ContentReference parentLink, string urlSegment)
            var list = new List<MatchingSegmentResult>();
            foreach (var child in LoadChildren<IContent>(parentLink, LanguageSelector.Fallback("en", true), -1, -1))
                var routable = child as IRoutable;
                var isMatch = routable != null && urlSegment.Equals(routable.RouteSegment, StringComparison.OrdinalIgnoreCase);

                if (isMatch)
                    list.Add(new MatchingSegmentResult
                        ContentLink = child.ContentLink
            return list;

        public IContent CreateMirroredContent(MappedIdentity mappedIdentity, PageData original)
            PageData mirrored = original.CreateWritableClone();

            mirrored.ParentLink = new PageReference(EntryPoint.ID);

            mirrored.ContentLink = mappedIdentity.ContentLink;
            mirrored.ContentGuid = mappedIdentity.ContentGuid;
            (mirrored as PageData).LinkType = PageShortcutType.Normal;
            (mirrored as IRoutable).RouteSegment = (original as IRoutable).RouteSegment;
            mirrored.LinkURL = "";
            return mirrored;

        /// <summary>
        /// The set cache settings.
        /// </summary>
        /// <param name="content">
        /// The content.
        /// </param>
        /// <param name="cacheSettings">
        /// The cache settings.
        /// </param>
        protected override void SetCacheSettings(IContent content, CacheSettings cacheSettings)
            if (content == null || cacheSettings == null)

            // Make the cache of this content provider depend on the original content
            cacheSettings.CacheKeys.Add(_cacheCreator.CreateCommonCacheKey(new ContentReference(content.ContentLink.ID)));

        protected override void SetCacheSettings(ContentReference contentReference, IEnumerable<GetChildrenReferenceResult> children, CacheSettings cacheSettings)
            // Set a low cache setting so new items are fetched from data source, but keep the
            // items already fetched for a long time in the cache.
            cacheSettings.SlidingExpiration = TimeSpan.FromSeconds(30);

            base.SetCacheSettings(contentReference, children, cacheSettings);


More reading Content Providers: