Per Magne Skuseth
Nov 14, 2014
  9059
(4 votes)

Content Providers 101 – Part II: From read-only to writable

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

Create and update

In order to update or write Person objects to the text file, we’ll need to override the Save method. The Save method will occur whenever a draft has been created, autosaved or published.  The drafts are kept in-memory in this example (in the _attendees property), but they could have been saved back to the source(PersonService) had the service supported it. They could also have been saved to the dynamic data store or similar data sources.

// will contain all draft versions of attendee content
private List<Attendee> _attendees = new List<Attendee>();
 
public override ContentReference Save(IContent content, SaveAction action)
{
    // we have a new version. Either the attendee has been autosaved, or it has been published or a new one has been created
    var newVersion = content as Attendee;
 
    // new content - someone want to create a new attendee from the UI
    if (newVersion.ContentLink == ContentReference.EmptyReference)
    {
        var person = new Person()
        {
            Name = newVersion.Name,
            Company = newVersion.Company,
            Email = newVersion.Email,
            Title = newVersion.Title
        };
        PersonService.CreatePerson(person);
        newVersion = ConvertToAttendee(person);
        return newVersion.ContentLink;
    }
    // if the current version has the WorkID set to 0, the save event has fired for the first time since a publish.
    if (newVersion.ContentLink.WorkID == 0)
    {
        // indicate that this is not the published version
        newVersion.Status = VersionStatus.CheckedOut;
        newVersion.ContentLink.WorkID = 1;
 
        // adding the version to the in-memory list
        _attendees.Add(newVersion);
    }
    else
    {
        // save has been triggered with a draft and the publish button has been hit by the editor
        if (action == SaveAction.Publish)
        {
            // check if the email has been changed. This is important as the email serves as the unique identifier in the IdentityMappingService
            var identifier = IdentityMappingService.Service.Get(newVersion.ContentLink).ExternalIdentifier;
            if (IdentityMappingService.Service.Get(MappedIdentity.ConstructExternalIdentifier(ProviderKey,newVersion.Email)) == null)
            {
                // email has been changed as no mapping was found with the identifier
                // First, delete the identifier with the old email, and then map the content with the new email
                // if we had not done this, all previous references to the content would have become invalid
                IdentityMappingService.Service.Delete(identifier);
                IdentityMappingService.Service.MapContent(MappedIdentity.ConstructExternalIdentifier(ProviderKey, newVersion.Email), newVersion);
            }
 
            // update the data using the PersonService. The text file will now be updated!
            PersonService.UpdatePerson(identifier.Segments[1], newVersion.Email, newVersion.Title, newVersion.Name, newVersion.Company);
 
            // WorkId and status are being reverted, and the working copies in _attendees are removed.
            newVersion.ContentLink.WorkID = 0;
            newVersion.Status = VersionStatus.Published;
            _attendees.RemoveAll(x => x.ContentLink.ID.Equals(newVersion.ContentLink.ID));
        }
    }
    return newVersion.ContentLink;
}

 

LoadContent also needs to be modified in order to load the in-memory items, and not just directly from the PersonService:

protected override IContent LoadContent(ContentReference contentLink, ILanguageSelector languageSelector)
{
    // check for a matching working copy
    var attendee = _attendees.FirstOrDefault(p => p.ContentLink.Equals(contentLink));
    if (attendee == null)
    {
        // In order to return the attendee, the contentLink must be mapped to an e-mail so that the person object can be found using the PersonService
        MappedIdentity mappedIdentity = IdentityMappingService.Service.Get(contentLink);
 
        // the email is found in the ExternalIdentifier that was created earlier. Not that Segments[1] is used due to the fat that the ExternalIdentifier is an Uri.
        // It contains two segments. Segments[0] contains the content provider key, and Segments[1] contains the unique path, which is the e-mail in this case.
        string email = mappedIdentity.ExternalIdentifier.Segments[1];
        attendee = ConvertToAttendee(PersonService.GetPersonByEmail(email));
    }
    return attendee;
}


With this in place, we can create and update content directly to the text file.

save_attendee

 

Delete

Being able to create, read and update, we should also be able to delete content. This is done by overriding Delete in the provider:

public override void Delete(ContentReference contentLink, bool forceDelete)
{
    var mappedIdentity = IdentityMappingService.Service.Get(contentLink);
    string email = mappedIdentity.ExternalIdentifier.Segments[1];
    PersonService.Delete(email);
    IdentityMappingService.Service.Delete(mappedIdentity.ExternalIdentifier);
}


To remove content from the text file, PersonService.Delete is invoked. The mapping in the IdentityMappingService is also deleted as we wouldn’t want lots of unused mapping laying around.

CRUD operations are now in place, but there are a few things left. We’ll put the finishing touches to it in Part III

Nov 14, 2014

Comments

Øyvind Jonassen
Øyvind Jonassen Jul 16, 2015 04:58 PM

Nice post. I am trying to do the same only with pages in episerver (multisite), all sites share the same news content with childs (With most capabilites on). Editors in all sites need to be able to edit translate, create ....

Do you have some nice code to share on that :)

public override ContentProviderCapabilities ProviderCapabilities
{
    get
    {
        return ContentProviderCapabilities.Search | ContentProviderCapabilities.MultiLanguage | ContentProviderCapabilities.Create | ContentProviderCapabilities.Edit | ContentProviderCapabilities.Copy | ContentProviderCapabilities.Delete | ContentProviderCapabilities.Security | ContentProviderCapabilities.Wastebasket;
    }
}

In edit mode everthing looks like its is workning but when i try to do a translate or somthing i get this :)

An exception of type 'System.NotSupportedException' occurred in EPiServer.dll but was not handled in user code

I have not writen content providers before so any help/code would be nice.

Cheers Øyvind

Øyvind Jonassen
Øyvind Jonassen Sep 1, 2015 04:51 PM

Hi Per Magne

If you have time :)

http://world.episerver.com/forum/developer-forum/-EPiServer-75-CMS/Thread-Container/2015/9/need-help-creating-a-content-provider/

Please login to comment.
Latest blogs
Keycloak Authentication with Optimizely CMS 12 and .NET Aspire

This post walks through how to wire together Optimizely CMS 12, Keycloak and .NET Aspire to give you a single local development environment that...

Andreas Ylivainio | Apr 13, 2026

CMS 12 - Optimizely DAM Integration 2.2.0

What's New in Optimizely DAM Integration 2.2.0 Version 2.2.0 of the Optimizely DAM (CMP) integration for CMS 12 is a pretty big release. Many of th...

Robert Svallin | Apr 12, 2026

CMS 13: The New Core Element of Your AI-First Optimizely Platform

CMS 13 is out and already brings solid AI-first foundations. With impressive features already shipped and an exciting roadmap ahead, this is the...

Michał Mitas | Apr 11, 2026 |

Deploying to Linux in SaaS (Configured) Commerce

With Optimizely SaaS (Configured) Commere now suporting net8.0 extensions dll, you will need to make a small tweak to how you distribute you...

Mark Hall | Apr 10, 2026 |

Optimizely CMS 13 and the Alloy demo site

The Alloy demo site now runs on Optimizely CMS 13. Here’s a quick guide to getting it up and running locally.

Tomas Hensrud Gulla | Apr 10, 2026 |

A day in the life of an Optimizely OMVP: Getting Up to Speed with Optimizely CMS 13 — A Free Learning Path

If you're working with Optimizely and haven't started exploring CMS 13 yet, now's the time. I've put together a dedicated CMS 13 course within the...

Graham Carr | Apr 10, 2026