shell/stores/metadata/episerver.core.contentdata does not return complete data on first request after site restart

Member since: 2002

I have an Episerver 10.9 site with a custom property (ImageVaults MediaReference) which has a custom EditorDescriptor.

This descriptor adds metadata to the property and this metadata is passed to the CMS UI using the contentdata metadata store.

Problem is, after a site restart, it seems to be a init issue when the first request to the store does not return the metadata added by the editor descriptor.

If I load a page in the CMS editor ui the browser will request the contentdata like below

http://mysite.local/EPiServer/shell/Stores/metadata/EPiServer.Core.ContentData?modelAccessor=%7B%22contentLink%22%3A%229_52%22%7D&dojo.preventCache=1494505103700

and return json data representing the properties and their metadata. (

Before the page is completely loaded, the UI will load the same url again but will then get another answer from the server
http://mysite.local/EPiServer/shell/Stores/metadata/EPiServer.Core.ContentData?modelAccessor=%7B%22contentLink%22%3A%229_52%22%7D&dojo.preventCache=1494505105982

If I check the differences between the returned json, i can see that in the first request

  • uiType property is null
  • layoutType is set to "epi/shell/layout/ParentContainer"
  • metadata is not set

In the second (and all following requests to the store) the data is correct.

If I then click the form edit button before the second request is done, my editors for my properties are not working at all.

First request (only my ArticleImage property)

{
	"name": "ArticleImage",
	"modelType": "ImageVault.EPiServer.MediaReference",
	"showForEdit": true,
	"hideSurroundingHtml": false,
	"uiType": null,
	"uiPackage": null,
	"customEditorSettings": {
		"overlayType": "imagevault/editors/PropertyMediaOverlaySelector"
	},
	"layoutType": "epi/shell/layout/ParentContainer",
	"initialValue": null,
	"displayName": "ArticleImage",
	"groupName": "Information",
	"displayOrder": 0,
	"settings": {
		"isLanguageSpecific": false,
		"fullName": "ArticleImage",
		"label": "ArticleImage"
	},
	"overlaySettings": {
		"fullName": "ArticleImage",
		"customType": "imagevault/editors/PropertyMediaOverlaySelector"
	},
	"additionalValues": {
		"dropTargetType": "ImageVaultMedia"
	},
	"mappedProperties": [],
	"properties": [],
	"groups": [],
	"selections": []
}

Second request

{
	"name": "ArticleImage",
	"modelType": "ImageVault.EPiServer.MediaReference",
	"showForEdit": true,
	"hideSurroundingHtml": false,
	"uiType": "imagevault/editors/PropertyMediaSelector",
	"uiPackage": null,
	"customEditorSettings": {
		"overlayType": "imagevault/editors/PropertyMediaOverlaySelector"
	},
	"layoutType": null,
	"initialValue": null,
	"displayName": "ArticleImage",
	"groupName": "Information",
	"displayOrder": 0,
	"settings": {
		"isLanguageSpecific": false,
		"fullName": "ArticleImage",
		"label": "ArticleImage",
		"metadata": {
			"name": "ArticleImage",
			"modelType": "ImageVault.EPiServer.MediaReference",
			"showForEdit": true,
			"hideSurroundingHtml": false,
			"uiType": null,
			"uiPackage": null,
			"customEditorSettings": {
				"overlayType": "imagevault/editors/PropertyMediaOverlaySelector"
			},
			"layoutType": "epi/shell/layout/ParentContainer",
			"initialValue": null,
			"displayName": "ArticleImage",
			"groupName": "Information",
			"displayOrder": 0,
			"settings": {
				"isLanguageSpecific": false,
				"fullName": "ArticleImage",
				"label": "ArticleImage"
			},
			"overlaySettings": {
				"fullName": "ArticleImage",
				"customType": "imagevault/editors/PropertyMediaOverlaySelector"
			},
			"additionalValues": {
				"dropTargetType": "ImageVaultMedia"
			},
			"mappedProperties": [],
			"properties": [],
			"groups": [],
			"selections": []
		}
	},
	"overlaySettings": {
		"fullName": "ArticleImage",
		"customType": "imagevault/editors/PropertyMediaOverlaySelector"
	},
	"additionalValues": {
		"dropTargetType": "ImageVaultMedia"
	},
	"mappedProperties": [],
	"properties": [],
	"groups": [],
	"selections": []
}

Is there any way around this or is it a bug?

#178481 May 11, 2017 15:34
  • Member since: 2002

    An easy way to reproduce is to create a page model with the custom property and making it [Required]. This will trigger edit mode when creating a new page and in that case the ContentData store will only be called once and the custom property editor will not load.

    (be sure to not start the site on the same page type as you are about to create since then the preview mode will load and the ContentData will already have been loaded (you can, of course, see the effects in fiddler but it will be more evident in the other case.)

    #178504 May 12, 2017 6:18
  • Member since: 2002

    A complement for the reproduction is to create an empty episerver site, reference ImageVault.EPiServer.UI nuget package and create a simple page model

    using System.ComponentModel.DataAnnotations;
    using EPiServer.Core;
    using EPiServer.DataAnnotations;
    using ImageVault.EPiServer;
     
    namespace WebApplication4.Models
    {
        [ContentType(GUID = "9Caa8A41-5C8a-4BE0-8E73-520FF3DE8889")]
        public class TestPage : PageData
        {
            [Required]
            public virtual MediaReference Image { get; set; }
        }
    }
    

    Then the first time you try to create the page you'll end up with an empty editor

    If you press cancel and tries to create the page again, the correct editor is displayed. 

    To reproduce, restart the website.

    #179112 May 31, 2017 15:39
  • Member since: 2002

    If you would like to reproduce without any third party tools, use the following c# class

    https://file.meridium.se/f/8048096c41/?dl=1

    and create a page template as follows

    using System.ComponentModel.DataAnnotations;
    using EPiServer.Core;
    using EPiServer.DataAnnotations;
    using WebApplication4.Example;
    
    namespace WebApplication4.Models
    {
        [ContentType(GUID = "9Caa8A41-5C8a-4BE0-8E73-520FF3DE8889")]
        public class TestPage : PageData
        {
            [Required]
            public virtual TestReference Test { get; set; }
        }
    }

    The TestReference is a data class to the PropertyTest Episerver property who has a custom editor. The editor itself is a bogus url and will crash the epi editor but the first time the page is loaded, the metata is incorrect and the page will load.
    Create Page first time

    If I then create a new page again

    New page

    The page will load the correct metadata and the bogus editor will generate javascript errors

    #179325 Edited, Jun 08, 2017 11:48
  • Thank you Dan, we're looking into it (bug id: CMS-7623).

    #179336 Jun 08, 2017 14:14
  • The problem seems to be in the editor descriptor. The properties are expected to be set in the constructor, or at least before the base EditorDescriptor.ModifyMetadata is called. In your sample, there are two ways to solve this:

    Preferably set it in the ctor:

            public PropertyTestEditorDescriptor()
            {
                ClientEditingClass = "some/bogus/path";
            }

    Or switch the order in the ModifyMetadata override so the properties exist before the base method is called and copies over the data:

            public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
            {
                ClientEditingClass = "some/bogus/path";
                base.ModifyMetadata(metadata, attributes);
            }

    That's most likely the issue with ImageVault as well.

    I see that this (and other) information is missing in the online documentation and have raised the issue internally. It should say this:

            /// <summary>
            /// Modifies the metadata, adding any custom data the client needs.
            /// </summary>
            /// <remarks>
            /// This method should only be overriden when you need the entire metedata object to work with.
            /// Otherwise, metadata properties should be set by setting the corresponding properties in the editor concrete descriptors' constructor.
            /// Also be aware that modifying metadata object will overwrite all data annotation attributes used in model class.
            /// </remarks>
            /// <param name="metadata">The metadata.</param>
            /// <param name="attributes">The custom attributes attached to the model class</param>
    

    #179504 Jun 14, 2017 11:13
  • Member since: 2002

    Ok, that clarifies the issue but I'm not satisfied.

    Preferably, the order in which the base and own implementation are invoked, should not be of importance. This is not any known best practice to invoke own code before calling base. In fact, the opposite order is often suggested (base first, then own implementation if the base modifies things you would like to correct).

    If the order is of importance, you need to specify this in the documentation and add it to the suggestion above.

    #179505 Jun 14, 2017 11:39
  • I only mentioned the second option because of how your code sample looked, but do note that the recommended way is setting the properties in the constructor and not override ModifyMetadata at all.

    #179507 Jun 14, 2017 13:10
  • Member since: 2002

    But we do modify metadata, we set a bunch of configurations and settings in the ModifyMetadata method.

    What is the recommended way to set these metadata? 
    (now I moved the base call and set it last).

    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes) {
        try {
            
            //Support for drag'n'drop
            metadata.AdditionalValues.Add("DropTargetType", "ImageVaultMedia");
    
            // Initiate the ClientEditingClass to the default class
            ClientEditingClass = "imagevault/editors/PropertyMediaListSelector";
    
            var fullPropName = new List<string>();
            metadata.FindTopMostContentMetadata(fullPropName);
    
            //Settings for Editform
            metadata.EditorConfiguration.Add("fullName", string.Join(PropertyResolver.DefaultScopeNameSeparator.ToString(), fullPropName));
    
            //Settings for inline edit
            metadata.OverlayConfiguration.Add("fullName", string.Join(PropertyResolver.DefaultScopeNameSeparator.ToString(), fullPropName));
    
            //Editor for the overlay
            //7.1 had the overlayType (we can keep this to maintain backwards compability
            metadata.CustomEditorSettings["overlayType"] = "imagevault/editors/PropertyMediaListOverlaySelector";
            //from 7.5 we need to add the customType property
            metadata.OverlayConfiguration["customType"] = "imagevault/editors/PropertyMediaListOverlaySelector";
        } catch (Exception e) {
            Log.Error("Failed creating metadata", e);
            //Select the error widget...
            ClientEditingClass = "imagevault/editors/PropertyMediaErrorWidget";
    
            //Obtain a better message based on the exception, if available 
            var message = PropertyMediaControlBase<PropertyMedia>.GetExceptionErrorMessageString(e);
            metadata.EditorConfiguration.Add("errorMessage", $"[{message}]");
        }
        base.ModifyMetadata(metadata, attributes);
    }
    

    #179521 Jun 14, 2017 17:01
  • Assigning to the properties on the instance like ClientEditingClass should be set in the ctor, so they can be used by base.ModifyMetadata. The rest you can keep where you have them, just move the base.ModifyMetadata call to the top like usual.

    You can also initialize the instance properties EditorConfiguration and OverlayConfiguration in the constructor (instead of calling "metadata.Editor/OverlayConfiguration.Add()" yourself) as those will also be copied over to the metadata object in base.ModifyMetadata. For future reference, if you want to do something extra for EditorConfiguration or OverlayConfiguration you can override SetEditorConfiguration and SetOverlayConfiguration respectively.

    #179548 Jun 15, 2017 8:49