How to get make a simple class that is a child of a Document/Page and gets synchronized correctly?

Joe Krill asked on December 12, 2014 18:08

I'm trying to create a custom class that is a child of a document/page. Similar to categories or tags -- but in my particular case I need to associate some external data with documents. For the sake of example, let's call this class ExternalReference, with the goal being that a document/page can have 0, 1, or many ExternalReferences associated with it. And -- most importantly -- that these get updated during synchronization.

So my ExternalReference class has the following fields:

  1. ExternalReferenceID A unique primary key auto-incrementing and auto-created.
  2. ExternalReferenceName The name of this reference.
  3. DocumentID The ID of the document that this entry belongs to. "Reference to" is set to "Page, and "Reference type" is set to "Binding".

My TYPEINFO is:

/// <summary>
/// Type information.
/// </summary>
public static ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(PayrollDepartmentInfoProvider), OBJECT_TYPE, "My.ExternalReference", "ExternalReferenceID", null, null, "ExternalReferenceName", "ExternalReferenceName", null, null, "DocumentID", "cms.document")
{
    ModuleName = "My.Module",
    TouchCacheDependencies = true
};

I've also created a UI element under "CMS > Administration > Content management > Pages > Edit" called "External References". This has an "Add" and "Edit" node under it, and this gives me a custom tab in the content tree to create these external references. I had to create a custom form extender that assigns the correct DocumentID value to my ExternalReference in the OnBeforeSave event, but beyond that I'm using the built-in Page Templates here (Object listing, New/Edit Object). This part seems to work for the most part. (Although when I change certain TYPEINFO properties I run into problems here).

My problem is that I can't get these external references to synchronize with the parent document through the staging module. I've tried so many things, including:

  1. Setting various properties in the TYPEINFO class to different values. Things like IncludeToSynchronizationParentDataSet, LogSynchronization, IncludeToVersionParentDataSet, LogEvents, ImportExportSettings, IsBinding, etc.

  2. Switching from "Binding" to "Required" for the DocumentID field. (What the heck is the difference between these two ANYWAY?)

  3. Changing various settings in the UI Element. Particularly the "Parent object type" -- this seems to have no effect.

This is driving me absolutely crazy. There's virtually no documentation on any of this stuff (which itself is a HUGE problem I've been having with ANY kind of custom development), so I have no idea how to get this working except by looking at the existing Kentico code that does similar things. I've looked at the DocumentCategoryInfo class that Kentico uses for assigning categories to documents, and I've tried to duplicate that exactly, and it still doesn't work.

Ultimately, I want adding/removing an external reference to trigger an update to the underlying document in the staging tasks. I would also like these references to be synchronized when I use the "Synchronize current page" functionality in the staging module. I have not been able to get either of these things to happen.

Does anyone have any idea how this is done?

Correct Answer

Vilém Jeniš answered on December 14, 2014 08:54

Hi!

For the "[DocumentHelper.CheckParameters]: Missing document parent node." error: A full stack trace would be more helpful, however I believe, that the system is telling you that you are trying to perform a 'touch parent' without having a parent.

Your problem is, that you can't use documents this way. The object API and document API are a bit different and thus the fact that it works at least somehow is somewhat a luck.

I'd strongly suggest a small redesign. If you need a class which inherits form a document you need a 'document type' (page type). A document type IS a class stored in the table CMS_Class only having a boolean flag 'ClassIsDocumentType' set to true. You can then configure whatever fields you need and it'll all work like with any other document.

If for some reason you need to combine it with the object API I suppose you could trick the system into it somehow by creating a new class called CONTENT.ExternalRefrence (to make the table name collide) and then copy all the properties you need and in your provider you'd use the Document API to handle your objects. This way the system would be able to list, reorder, modify, edit, ... (Given you provide correct methods) your object as if it were a classic object with a normal provider like you create here https://docs.kentico.com/display/K81/Creating+custom+modules#Creatingcustommodules-Addingclassestomodules, but since your provider uses the document API under the hood all the necessary things that documents require would be taken care of. Including synchronization. Also. Your objects will be manageable from the Pages app as well as your custom module's UI if you create it.

Disclaimer: However awesome and useful this might seem, it's not a recommended or supported scenario to make table names collide or combining the Document/Object approach to a single class. However I think it might work and suit your needs.

Vilém

0 votesVote for this answer Unmark Correct answer

Recent Answers


Joe Krill answered on December 12, 2014 23:35

As an update, I've made SOME progress. I can get the individual ExternalReference objects to synchronize correctly individually, when they are added, updated and delete, but I'm still unable to to get them to synchronize completely when I synchronize the parent document/page. I've also added them to the Objects synchronization tree, but when I do a complete synchronization there, it only updates and inserts -- it doesn't remove non-existing objects from the destination server.

public static ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(typeof(ExternalReferenceInfoProvider), OBJECT_TYPE, "My.ExternalReference", "ExternalReferenceID", null, null, "ExternalReferenceName", "ExternalReferenceName", null, null, "DocumentID", "cms.document")
{
    ModuleName = "My.Module",
    TouchCacheDependencies = true,
    IncludeToSynchronizationParentDataSet = IncludeToParentEnum.Complete,
    LogSynchronization = SynchronizationTypeEnum.LogSynchronization,
    IncludeToVersionParentDataSet = true,
    SupportsVersioning = false,
    AllowTouchParent = false,
    LogEvents = true,
    ImportExportSettings =
    {
        IncludeToExportParentDataSet = IncludeToParentEnum.Complete,
        LogExport = false,
    },
    SynchronizationObjectTreeLocations = new List<ObjectTreeLocation>()
    {
        new ObjectTreeLocation(new string[2]
        {
            "##SITE##",
            "##CONTENTMANAGEMENT##"
        }),
    }
};

Without setting the AllowTouchParent to false, I get an error whenever I try to add a new ExternalReference: "[DocumentHelper.CheckParameters]: Missing document parent node." I have no clue what this means.

0 votesVote for this answer Mark as a Correct answer

Joe Krill answered on December 15, 2014 13:34

Well I do have a special 'document type'/'page type'. The problem is, that particular document type needs to have these extra attributes ('ExternalReference's) attached to it -- and it's a one-to-many relationship with those attributes. I suppose I could add a single field to that particular document type that would contain a delimited list of these attributes, but that makes querying on those attributes cumbersome, difficult to deal with, and probably quite slow.

So if there's no better solution here, I think I can (sort-of) work around this by hooking into some of the StagingTasks if I want this stuff to synchronize when the document itself synchronizes. But that's only part of the problem.

I'm still having a problem where running the synchronization on my objects (ExternalReferences) only performs updates and inserts -- it won't remove additional entries from the target server that do not exist on the staging server. It seems that this is by design, because I see the same behavior when I synchronize other objects (for example, when if I synchronize Media Libraries, it does not remove media from the target server that don't exist)? But is there any way to alter this behavior and tell the synchronization engine to do a "complete" synchronization where the target server removes non-existing entries?

0 votesVote for this answer Mark as a Correct answer

Vilém Jeniš answered on December 16, 2014 12:44 (last edited on December 16, 2014 12:57)

Well... Did you try 'touching' the parent document when setting/deleting your 'external reference' ? Since you implement the InfoProvider you should be able to do it.

The staging works in this way: When you delete an object in the staging environment a staging task 'Delete object XYZ' is created. When synchronizing, the task is executed on all production servers. They all attempt to delete object 'XYZ'. If the object doesn't exist it may result in an error or they might just skip it. This should be in the documentation I'm not sure about this detail. Same goes for modifying an object or creating. However you can't make staging delete an object that didn't exist in the staging environment in the first place. You can also modify what kinds of tasks are created/logged in the settings app.

I can't think of any really cool/easy and efficient way of implementing the kind of synchronization you need. I believe simply not creating any objects in the production should do it. But I'm sure someone might come up with a bright idea.

0 votesVote for this answer Mark as a Correct answer

Joe Krill answered on December 17, 2014 15:20

Touching the parent doesn't help with synchronizing these attributes when the parent itself is synchronized.

For example, if I manually synchronize the content tree (or a subset of it), there's really no way to include my child objects in that synchronization.

0 votesVote for this answer Mark as a Correct answer

Vilém Jeniš answered on December 17, 2014 16:51

Well no... unless you touch the child objects as well. You could eventually do that in a GlobalEvent handler reacting to a document being saved. In that method you'd verify the page type and proceed to touch necessary objects.

This should help I think.

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.