Per Magne Skuseth
Nov 14, 2014
  6375
(6 votes)

Content Providers 101 – Part III: Finishing Touches

This is the final part of my blog post series Content Providers 101. Make sure you’ve read Part I and Part II before reading this!

The provider is now almost complete, we just need to put the finishing touches to it.

Searching: In the component, there is a search box. In order to get it to return actual items, a search provider is needed

[SearchProvider]
public class AttendeeSearchProvider : ContentSearchProviderBase<Attendee, ContentType>
{
    private readonly AttendeeProvider _attendeeProvider;
 
    public AttendeeSearchProvider(LocalizationService localizationService, SiteDefinitionResolver siteDefinitionResolver,
        IContentTypeRepository<ContentType> contentTypeRepository, IContentProviderManager contentProviderManager)
        : base(localizationService, siteDefinitionResolver, contentTypeRepository)
    {
        _attendeeProvider = contentProviderManager.GetProvider(AttendeeProvider.Key) as AttendeeProvider;
    }
 
    public override IEnumerable<SearchResult> Search(Query query)
    {
        IEnumerable<Person> people = PersonService.Search(query.SearchQuery);
        foreach (var attendee in people.Select(x => _attendeeProvider.ConvertToAttendee(x)))
        {
            yield return this.CreateSearchResult(attendee);
        }
    }
 
    public override string Area { get { return AttendeeProvider.Key; } }
    public override string Category { get { return AttendeeProvider.Key; } }
    protected override string IconCssClass { get { return "attendee-search-icon"; } }
}

To connect the search provider to the attendee component, override and match the SearchArea property on the repository descriptor (AttendeeRepositoryDescriptor) to the Area property in the search provider:

public override string SearchArea { get { return AttendeeProvider.Key; } }

 

The search provider will work for both the component and for the global navigation search

attendee_search

 

Speaking of search, if you are using EPiServer Find, the attendee data will automatically be indexed to Find when when running the content indexing job. This happens as Find will traverse the entire content tree and index all the IContent there is, including the “attendees” content folder beneath the root node.
This means that you can get attendees as a part of UnifiedSearch with just a single line of code:

SearchClient.Instance.Conventions.UnifiedSearchRegistry.Add(typeof(Attendee));

 

Preview: EPiServer offers a great on-page editing experience, so we should enable this for attendee content as well.
The preview controller in Alloy only works with BlockData, but can easily modified to work with attendees as well:

[TemplateDescriptor(
    Inherited = true, TemplateTypeCategory = TemplateTypeCategories.MvcController, 
    Tags = new[] { RenderingTags.Preview, RenderingTags.Edit },
    AvailableWithoutTag = false, ModelType = typeof(BlockData))]
[TemplateDescriptor(
    TemplateTypeCategory = TemplateTypeCategories.MvcController, 
    Tags = new[] { RenderingTags.Preview, RenderingTags.Edit },
    AvailableWithoutTag = false, ModelType = typeof(Attendee))]
[VisitorGroupImpersonation]
public class PreviewController : ActionControllerBase, IModifyLayout


What I’ve done is to remove the IRenderTemplate<BlockData> interface, and instead specified the model type in the TemplateDescriptor. Since TemplateDescriptors allows multiple attributes, I’ve added one for attendees as well.

 

Change context: When double-clicking blocks or media in the assets pane, the clicked item will be loaded and displayed. In order to get the same behavior for our attendees, we need to specify this in the AttendeeRepositoryDescriptor:

public bool ChangeContextOnItemSelection{get{return true;}}


The HierarchialList component will look for a matching property. If it does not exists or is set to false, the context will not change when double clicking the item, and you will have to use the edit button from the drop down menu in order to load and see the item.

 

Custom icon: In the assets pane, the icon that is being used for attendees is the standard content icon. In order to get one step closer to UX nirvana, a more descriptive icon should be used instead. This is done  by creating a UIDescriptor:

[UIDescriptorRegistration]
public class AttendeeUiDescriptor : UIDescriptor<Attendee>
{
    public AttendeeUiDescriptor()
    {
        IconClass = "epi-iconUser";
    }
}


attendee_with_icon

Tip: See lists of icons and corresponding css class names at ux.episerver.com

 

Try it!

Want to try it out? Get the source code here
Also: A big thanks to Linus Ekström for being super helpful!

Nov 14, 2014

Comments

Emanuel Herteliu
Emanuel Herteliu Jul 12, 2017 11:42 AM

Thank you for posting this!

Tried your example modifying the Attendee class to inherit PageData so the results get displayed in a ContentPage container as pages.

The issue is we can't get the ContentProviderResult using DataFactory.FindPagesWithCriteria.

Snippet

ContentRepository.GetChildren<>Attendee>(page.PageLink); -> works ('page' is the container ContentPage mentioned earlier)

Snippet
PropertyCriteriaCollection crits = new PropertyCriteriaCollection();
       PropertyCriteria crit = new PropertyCriteria();
       crit.Name = "EPI:MultipleSearch";
       crit.Value = "attendees";
DataFactory.Instance.FindPagesWithCriteria(page.PageLink, crits); -> won't work

Also tried crit.Value = "*"

In the initialization module did Snippet
searchClient.Conventions.UnifiedSearchRegistry.Add(typeof(Attendee));

Any idea why using GetChildren of ContentRepository works but not FindPagesWithCriteria....?

Also.... a second question: the pages returned by the ContentProvider get displayed in the tree structure in CMS but when clicking show 'All Properties' it will load forever...
any idea why?

The reason why we are trying to implement the ContentProvider is because we have entities from an external Database that we want to route to an Episerver ContentPage and trying a partial router didn't work
because our entities don't inherit from IContent. That's why we're trying to implement the custom ContentProvider: to bring the entities from the external DB as Episerver pages which
we will route.

Please login to comment.
Latest blogs
The Fragment Conundrum

Explore the challenges and opportunities of working with Optimizely SaaS, GraphQL fragments, and headless CMS architectures. Learn practical...

Andy Blyth | May 13, 2026 |

Optimizely CMS 13: What Actually Changed and Why It Matters

I had the privilege of attending a deep-dive session on CMS 13 this week, and after seeing the full roadmap laid out across these slides, I wanted ...

Aniket | May 12, 2026

Introducing the Optimizely MCP Server: AI That Speaks Commerce

MCP AI Commerce B2B Claude ChatGPT OpenAI Optimizely Insite Commerce Introducing the Optimizely MCP Server : AI That Speaks Commerce We've connecte...

Vaibhav | May 12, 2026

AEO, GEO and SEO with Epicweb AI Assistant in Optimizely CMS

Traditional SEO remains important, but content must now also be optimized for answer engines and generative AI. This article explains how the Epicw...

Luc Gosso (MVP) | May 11, 2026 |

Accelerating Optimizely CMS and Commerce upgrades with agentic AI (Part 1 of 2)

How Niteco's Upgrade Machine   uses orchestrated AI coding agents to deliver a buildable baseline and a running CMS, then hands over for...

Hung Le Hoang | May 11, 2026

Commerce 15 and CMS 13: Optimizely’s Next Step Toward AI-Powered, Graph-First Commerce

Optimizely is preparing to release Commerce 15 in mid-May 2026 , positioning this as a foundational shift—not just an upgrade. The direction is...

Augusto Davalos | May 7, 2026