Detecting when a part is added to a type

Topics: Core, Customizing Orchard, Writing modules
Developer
May 14, 2013 at 5:59 PM
Edited May 22, 2013 at 7:04 PM
I have a custom ContentPart which has data that is pre-calculated.
The data in this content part is calculated when the content item is published.

This works fine for me so far, except I needed to backfill content when the part is added to a Content Type. I added an event handler to ContentDefinitionService wanted to know if anyone else sees a need for this functionality... I can add it to core.

[See Below]
Developer
May 22, 2013 at 7:39 PM
Edited May 22, 2013 at 7:50 PM
So I'm a bit stuck, hoping to get some help.

I have a ContentPart I am trying to put together called OriginallyPublished.
My intent is to have a field in this part capture when content is first published and save that DateTime.
I can then use this part in projections, allowing the content to be modified/unpublished/republished without affecting the order.
namespace Contrib.OriginallyPublished.Models
{
    public class OriginallyPublishedPart : ContentPart
    {
    }

    public class OriginallyPublishedRecord : ContentPartRecord
    {
    }
}

namespace Contrib.OriginallyPublished
{
    public class Migrations : DataMigrationImpl
    {
        public int Create()
        {

            ContentDefinitionManager.AlterPartDefinition(
                typeof(OriginallyPublishedPart).Name,
                builder => builder
                    .Attachable()
                    .WithField("OriginallyPublished", cfg => cfg
                        .OfType("DateTimeField")
                        .WithDisplayName("Originally Published"))
                );

            return 1;
        }
    }
}

public class OriginallyPublishedHandler : ContentHandler
{
    private readonly IClock _clock;

    public OriginallyPublishedHandler(IRepository<OriginallyPublishedRecord> repository, IClock clock)
    {
        _clock = clock;
            
        Filters.Add(StorageFilter.For(repository));

        OnPublishing<OriginallyPublishedPart>(AssignOriginallyPublishedDate);
    }

    protected void AssignOriginallyPublishedDate(PublishContentContext context, OriginallyPublishedPart part)
    {
        var dateTimeField = ((dynamic) part).OriginallyPublished;

        DateTime originallyPublished = dateTimeField.DateTime;

        if (originallyPublished == DateTime.MinValue)
        {
            dateTimeField.DateTime = _clock.UtcNow;
        }
    }
}
This much works perfect, but I would now like to backfill the OriginallyPublished data when the
I realized I needed to capture events from the ContentDefinitionService. I crafted the following two classes and utilized them in ContentDefinitionService.cs (diff)
namespace Orchard.ContentTypes.Services
{
    public interface IContentDefinitionEventHandler : IEventHandler
    {       
        void AddingType(string name, string displayName);
        void AddedType(string name, string displayName);

        void RemovingType(string name, bool deleteContent);
        void RemovedType(string name, bool deleteContent);
        
        void AddingPart(string name);
        void AddedPart(string name);
        
        void RemovingPart(string name);
        void RemovedPart(string name);

        void AddingPartToType(string partName, string typeName);
        void AddedPartToType(string partName, string typeName);

        void RemovingPartFromType(string partName, string typeName);
        void RemovedPartFromType(string partName, string typeName);
        
        void AddingFieldToPart(string fieldName, string displayName, string fieldTypeName, string partName);
        void AddedFieldToPart(string fieldName, string displayName, string fieldTypeName, string partName);
        
        void RemovingFieldFromPart(string fieldName, string partName);
        void RemovedFieldFromPart(string fieldName, string partName);
    }

    public class BaseContentDefinitionEventHandler:IContentDefinitionEventHandler {
        public virtual void AddingType(string name, string displayName) {}

        public virtual void AddedType(string name, string displayName) {}

        public virtual void RemovingType(string name, bool deleteContent) {}

        public virtual void RemovedType(string name, bool deleteContent) {}

        public virtual void AddingPart(string name) {}

        public virtual void AddedPart(string name) {}

        public virtual void RemovingPart(string name) {}

        public virtual void RemovedPart(string name) {}

        public virtual void AddingPartToType(string partName, string typeName) {}

        public virtual void AddedPartToType(string partName, string typeName) {}

        public virtual void RemovingPartFromType(string partName, string typeName) {}

        public virtual void RemovedPartFromType(string partName, string typeName) {}

        public virtual void AddingFieldToPart(string fieldName, string displayName, string fieldTypeName, string partName) {}

        public virtual void AddedFieldToPart(string fieldName, string displayName, string fieldTypeName, string partName) {}

        public virtual void RemovingFieldFromPart(string fieldName, string partName) {}

        public virtual void RemovedFieldFromPart(string fieldName, string partName) {}
    }
}
So now I have what I need from Core to do what I want, unfortunately here is where I have the problem.
I'm not quite sure how to populate the field as it doesn't exist yet.

I made the assumption that AddedPartToType would be called after the part was added to the Type.
Am I not correct?
public class OriginallPublishedPartEventHandler : BaseContentDefinitionEventHandler
{
    private readonly IContentManager _contentManager;

    public OriginallPublishedPartEventHandler(IContentManager contentManager)
    {
        _contentManager = contentManager;
    }

    public override void AddedPartToType(string partName, string typeName)
    {
        base.AddedPartToType(partName, typeName);

        if (partName != "OriginallyPublishedPart")
            return;

        var contentItems = _contentManager.Query(new[] { typeName }).List();
        foreach (var contentItem in contentItems)
        {
            var dynamicContentItem = (dynamic)contentItem;

            var commonPart = (CommonPart)dynamicContentItem.CommonPart;

            if (!commonPart.PublishedUtc.HasValue)
                continue;

            var hasOriginallyPublishedPart = contentItem.Has<OriginallyPublishedPart>();

            Debug.Assert(hasOriginallyPublishedPart, "hasOriginallyPublishedPart = true");

            /*
            * What I would like to do, but can't because the part is null
                
            var originallyPublishedPart = dynamicContentItem.OriginallyPublishedPart;
            var originallyPublishedField = originallyPublishedPart.OriginallyPublished;
            var originallyPublishedValue = originallyPublishedField.DateTime;

            if (originallyPublishedValue != DateTime.MinValue)
                continue;

            originallyPublishedField.DateTime = commonPart.PublishedUtc.Value;
                
            */
        }
    }
}