11

I am creating an eventsystem function that is triggered when a new Page is created. The function should, when a new Page is created, set the metadata Schema of that Page, and create a Component that is then inserted in the Page metadata Component link field.

This function works (see sample code below), however, I have noticed that this function is triggered twice (as two Components are being created) Also, when I debug this function, it hits the breakpoint twice.

To confirm that nothing inside the code below could trigger a second load event, I commented out all code after the first if, but still this is being triggered twice.

I also tried to change the event phase to initiated. This is only triggered once, but it seems I can't change anything to the Page at that point (any changes I do are ignored).

I'm not sure if this is because I'm doing something wrong, or if this is by design, or this is a bug. Anyone has any suggestions ?

Event system code :

public ContentCreation()
{
    EventSystem.Subscribe<Page, LoadEventArgs>(OnPageCreate, EventPhases.Processed);
}

public void OnPageCreate(Page page, LoadEventArgs args, EventPhases phases)
{
    if (page.Id == TcmUri.UriNull)//we have a new page, so setup the schema
    {
        Schema meta = page.Session.GetObject([WEBDAV_PATH]) as Schema;
        page.MetadataSchema = meta;
        page.Metadata = meta.GetInstanceData(page.OrganizationalItem, LoadFlags.Expanded).Metadata;
        //now, create a new SEO component
        string SeoSchemaUri = [WEBDAV_PATH];
        string SeoFolderUri = [WEBDAV_PATH];
        if (page.Session.IsExistingObject(SeoFolderUri) && page.Session.IsExistingObject(SeoSchemaUri))
        {
            Folder folder = page.Session.GetObject(SeoFolderUri) as Folder;
            Schema seoschema = page.Session.GetObject(SeoSchemaUri) as Schema;
            Component comp = folder.GetNewObject<Component>();
            comp.Title = string.Format("Auto_Component_page_{0}", Guid.NewGuid().ToString("N"));
            comp.Schema = seoschema;
            comp.Content = seoschema.GetInstanceData(folder, LoadFlags.Expanded).Content;
            comp.Save(true);
            ItemFields pageMeta = new ItemFields(page.Metadata, page.MetadataSchema);
            ComponentLinkField field = pageMeta["relatedContent"] as ComponentLinkField;
            field.Value = comp;
            page.Metadata = pageMeta.ToXml();
        }
    }
}

Update

As requested an extract of my Tridion.ContentManager.config:

<extensions>
  <add assemblyFileName="C:\Program Files (x86)\Tridion\bin\Tridion.AudienceManagement.ContentManagerEvents.dll" /> 
  <add assemblyFileName="C:\Program Files (x86)\Tridion\bin\Tridion.UGC.EventSystem.dll" /> 
  <add assemblyFileName="C:\Program Files (x86)\Tridion\bin\Extensions\EventSystem.dll" />
</extensions> 

The C:\Program Files (x86)\Tridion\bin\Extensions\EventSystem.dll is my eventsystem dll, the other ones are standard Tridion ones.

Update 2

The reason why I subscribe to the Page, LoadEventArgs is because we want to set some default data when a Page is created, so the editor can modify it if needed. We don't want to do this on save (this would mean the editor would have to create a Page, save it, reopen it and then change the data).

  • small addition : contrary to what i said before, the initiated phase is also triggered twice – Harald Hoffelinck Oct 23 '13 at 8:33
  • Could you please provide the contents of your <extensions> node in Tridion.ContentManager.config? – Jonathan Primmer Oct 23 '13 at 8:35
  • @HaraldHoffelinck I've moved your comment in the question as an update, that makes it better readable for everybody – Bart Koopman Oct 23 '13 at 9:47
  • Using the Load event so the user can see the changes before saving the page makes a lot of sense. I just read creation of the page as in save the Page for the fist time, instead of create in the GUI. :) – Jan H Oct 23 '13 at 10:25
6

After having a little chat with my good friend Likhan we came to a solution:

So first my findings on 2013 (2011 might not be completely similar as there could be a legacy TOM call there). I can confirm that the EventPhases.Initiated is of no use, since setting metadata in there will be too early for it to even show up. So I only played with the EventPhases.Processed subscribing to Page, LoadEventArgs.

My event is triggered multiple times, and twice with the condition page.Id == TcmUri.UriNull being true. Let me describe both triggers:

  1. Triggered right after I created a new Page in the UI, and before the Page is fully loaded. When the event is done, the Page shows with the metadata Schema set and a Component linked (only one Component is created at this time).

  2. Triggered when I press Save and Close, Page.Title is "New Page" (even though I changed the title into something else). page.MetadataSchema is Null and page.Metadata also, hence a new Component is created (again).

The explanation for this comes from Likhan:

  1. load is triggered by CoreServiceBase.GetDefaultData, it is to get the default data for the UI object.

  2. load is triggered by CoreServiceBase.Create. When the Page is saved, the UI builds up a Page Delta XML and hands that over to the Core Service to create the new Page. So the Core Service has to load a new (empty) Page first and then sets the Delta XML in it and saves it. Hence the second trigger (which is useless to you).

So on to the Solution:

Besides checking if page.Id == TcmUri.UriNull, you also need to see in the Environment.StackTrace through which Method you are called. I initially thought this information would be available inside the args.EventStack property, but since we are talking about a .Net StackTrace, and not a Tridion event trace, we have to do it differently.

Now Environment.StackTrace is a string, and we could check if it contains CoreServiceBase.GetDefaultData or CoreServiceBase.Create, but the Contains method on that string is rather heavy so not good for performance. My solution is as follows:

public ContentCreation()
{
    EventSystem.Subscribe<Page, LoadEventArgs>(OnPageCreate, EventPhases.Processed);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static void OnPageCreate(Page page, LoadEventArgs args, EventPhases phases)
{
    // only react on new Pages
    if (page.Id == TcmUri.UriNull) 
    {
        StackTrace stackTrace = new StackTrace();
        foreach (var frame in stackTrace.GetFrames())
        {
            // build Class.Method
            MethodBase method = frame.GetMethod();
            string name = method.ReflectedType.Name + "." + method.Name;

            // only trigger on CoreServiceBase.GetDefaultData, not on CoreServiceBase.Create (which is the second call)
            if (name.Equals("CoreServiceBase.GetDefaultData")) break;
            if (name.Equals("CoreServiceBase.Create")) return;
        }

        const string MetadataFieldName = "relatedContent";
        string PageMetadataSchemaUri = string.Format("tcm:{0}-209-8", page.OrganizationalItem.Id.PublicationId);
        string SeoSchemaUri = string.Format("tcm:{0}-520-8", page.OrganizationalItem.Id.PublicationId);
        string SeoFolderUri = string.Format("tcm:{0}-1-2", page.OrganizationalItem.Id.PublicationId);

        if (page.MetadataSchema == null)
        {
            // set Page metadata Schema
            Schema meta = (Schema)page.Session.GetObject(PageMetadataSchemaUri);
            page.MetadataSchema = meta;
            page.Metadata = meta.GetInstanceData(page.OrganizationalItem, LoadFlags.Expanded).Metadata;

            Logger.Write(string.Format("Added metadata to Page {0} ({1})", page.Title, page.Id), "ContentCreationHandlerExtension", LoggingCategory.General, TraceEventType.Information);
        }

        // check if Component hasn't already been set
        ItemFields pageMeta = new ItemFields(page.Metadata, page.MetadataSchema);
        if (pageMeta.Contains(MetadataFieldName))
        {
            ComponentLinkField field = (ComponentLinkField)pageMeta[MetadataFieldName];
            if (field != null && field.Value == null)
            {
                // create a new SEO Component
                Folder folder = (Folder)page.Session.GetObject(SeoFolderUri);
                Schema seoSchema = (Schema)page.Session.GetObject(SeoSchemaUri);
                Component comp = folder.GetNewObject<Component>();
                comp.Title = string.Format("Auto SEO Component {0}", Guid.NewGuid().ToString("N"));
                comp.Schema = seoSchema;
                comp.Content = seoSchema.GetInstanceData(folder, LoadFlags.Expanded).Content;
                comp.Save(true);
                Logger.Write(string.Format("Created Component {0} ({1})", comp.Title, comp.Id), "ContentCreationHandlerExtension", LoggingCategory.General, TraceEventType.Information);

                // set link in Page metadata
                field.Value = comp;
                page.Metadata = pageMeta.ToXml();

                Logger.Write(string.Format("Added Component Link to metadata of Page {0} ({1})", page.Title, page.Id), "ContentCreationHandlerExtension", LoggingCategory.General, TraceEventType.Information);
            }
        }
    }
}

I added some logging for myself to see what happens, but this seems to work fine in all cases, creating only one Component per new Page.

  • you are right. I found out there are several actions triggering the load event (even saving triggers this load event first, so it will be by design). I'm currently indeed focussing in finding a way how to make sure the component is only set once – Harald Hoffelinck Oct 23 '13 at 10:38
  • @HaraldHoffelinck Would simply reading the metadata and seeing if the Component link field has a value not be sufficient? – Bart Koopman Oct 23 '13 at 11:46
  • No luck with the eventstack, not even with the metadata property. Even stranger : if you edit the newly created page and then click save, the load event still runs the first time with metadata set to null At first sight, i also can't find any properties that distinguis the different calls. I'm continuing to search. – Harald Hoffelinck Oct 23 '13 at 12:39
  • 1
    During Initiated of LoadEventArgs phase the Metadata along with all other properties might still be null, as it has not yet been retrieved from the DB. Could you confirm if the Load event is also fired twice if you do the same operation from TOM.NET directly. My guess is that as you are using 2011 where the UI still uses legacy TOM API, it has to load it once in TOM and once again when it is passed to TOM.NET for actual save. So, in order to detect if the component has already been created and added to page subscribe to Processed phase. – Likhan Oct 24 '13 at 7:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.