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.
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.
ClonedByCategoryContentProvider
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, IsLeafNode=true }; list.Add(result); } } 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) { return; } // 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); } } }
GIST: https://gist.github.com/LucGosso/
More reading Content Providers: