Try our conversational search powered by Generative AI!

Saving Custom IContent - Setting "Name" Property Does Not Persist If No Other Properties Are Set


I noticed some strangeness when saving custom pieces of IContent, and I'm not sure if I've stumbled onto a bug, or if I misunderstand how this should work.  When I create a piece of IContent, and try to set the "Name" property (nothing else), the value does not persist when calling ContentRepository.Save().

For example, if I have the following custom IContent:

[ContentType(DisplayName = "CustomContent", GUID = "1bf9265d-64c2-46d9-8349-ad57814b0d57", Description = "")]
public class CustomContent : ContentDataIContent
    public bool IsDeleted { getset; }
    public virtual string Name { getset; }
    public ContentReference ContentLink { getset; }
    public ContentReference ParentLink { getset; }
    public Guid ContentGuid { getset; }
    public int ContentTypeID { getset; }

    public virtual string Test { getset; }

And then try to save it:

var logger = LogManager.GetLogger();
var contentRepository = ServiceLocator.Current.GetInstance<>IContentRepository>();
var customContent = contentRepository.GetDefault<>CustomContent>(ContentReference.RootPage);
customContent.Name = "Test Name";
contentRepository.Save(customContent, AccessLevel.NoAccess);
customContent = contentRepository.Get<>CustomContent>(customContent.ContentLink);
logger.Info($"CustomContent name:  {customContent.Name}"); //prints nothing for name because it's null

The name property does not persist after calling save.  However, it works as expcted if I change the code so that the "Test" property that is defined on CustomContent is also set (instead of just the name):

var logger = LogManager.GetLogger();
var contentRepository = ServiceLocator.Current.GetInstance<>IContentRepository>();
var customContent = contentRepository.GetDefault<>CustomContent>(ContentReference.RootPage);
customContent.Name = "Test Name";
customContent.Test = "Test"; contentRepository.Save(customContent, AccessLevel.NoAccess); customContent = contentRepository.Get<>CustomContent>(customContent.ContentLink); logger.Info($"CustomContent name:  {customContent.Name}"); //prints "Test Name" for the name

The above code is simplified.  In my actual implementation, I have a custom ContentRepositoryDescriptor and NavigationComponent wired up to allow editing the custom IContent in the UI.  The UI performs similarly when modifying an existing receord:  "Name" doesn't even trigger the "is modified" logic to allow publishing. 

Is this a bug, or am I missing something?

Edited, Feb 21, 2018 23:16

What version are you using? Helps to determine if it is a bug or not.

Where are these CustomContent located? What ContentProvider is used?

Feb 22, 2018 18:04

This project is on version 11.3.0 (sorry, thought I included that in my original post).  I am using the default ContentProvider.  I'm essentially doing what this blog post is describing.  It seems strange that everything works as expected when setting a property other than Name.

Feb 22, 2018 18:18

Somewhere in the back of my head there is a notion we reported a bug that it doesn't save if only the name is changed, but i thought it had been fixed already. Of course when i go in search of it now i can't find it. :(

Feb 26, 2018 12:24

Is this something I should try to file a bug for then?  I can hack a workaround together (add a second, duplicated "name" property), but I'd rather it just work as intended...

Mar 07, 2018 23:03

Yes file a bug with Episerver development support, worst case scenario they will tell you in what version it has been fixed in. :D

Mar 08, 2018 13:38

When you set the value of a property, ContentData.IsModified is set to true. The exception is the properties on IContent which doesn't trigger a change to IsModified. So in your example, IsModified is false both before and after setting customContent.Name = "Test Name" but will be true after customContent.Test = "Test". The data access layer will determine if a content needs to be saved based on IsModified.

Solution, implement IContent properties with something like:

private string _name;

public string Name
    get { return _name; }
        if (!string.Equals(value, _name))
            _isModified = true;
            _name = value;

private bool _isModified;

public bool IsModified
    get { return _isModified; }

public void ResetModified()
    _isModified = false;

You can also inherit from BasicContent instead of ContentData which has this implememented.

Post edited to also include the implementation of IModifiedTrackable.

Edited, Mar 09, 2018 15:36

Interesting.  Inheriting from BasicContent does indeed make things work as expected.  I never would have thought to implement my own "is dirty" tracking on a property, but I guess the IContent properties must be treated differently than other, custom properties (which explains why the "Test" property worked as expected).

Everything I've seen (tutorials, etc.) demonstrates inheriting from ContentData.  In fact, I'm pretty sure that was even mentioned in the training/bootcamp. The documentation could certainly use some clean up when it comes to using custom IContent.  Anyway, I decompiled EPiServer.dll and took a look at the EPiServer.Core namespace, and I think I figured out what each of the various "base classes" for content are meant to do:

  • ContentData:  Base class for all content - probably shouldn't be used on its own except in very specific scenarios.
  • BasicContent:  Base class for non-versioned content - provides created/deleted/changed properties as well as proper IsModified tracking
  • ContentBase:  Base class for versioned content - provides versioning properties, and adds the "Category" property
Mar 09, 2018 16:45
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.