4

I understand (at least) two ways to implement Page Metadata:

  1. Create a schema of type 'Embeddable'. Add it in e.g. 'Home Page' tab 'Metadata'. When "Use this Page Schema as Metadata Schema" it is used and can be filled in. JSON generated for Page like this ('metadata' is filled name):
    enter image description here enter image description here

  2. Create a schema type 'Metadata' and select it on a specific page. Then Page JSON is something like this:
    enter image description here



Question-1: How to implement strong name for Metadata Schema?
Both options comes in Razor view back as dictionary e.a. @Model.Meta["publishdate"] You have to check fields before usage.

enter image description here

Question-2: What is the way to go to collect metadata at page level?

  1. General (embedded) metadata schema is added to each page schema. Works OOTB.
  2. Add metadata to components and extract that to page level. How to do that?
  3. Add metadata to Structure Group and get that in metadata with the pages. Some field should be added for Page as metadata it self.

UPDATE-1: CustomPageModel.cs should do proper mapping to any meta fields. Below the code and values I tried for mapping. Example use 2 fields and 1 embedded schema to catch.

    [Serializable]
    [SemanticEntity(EntityName = "artikelPage", Vocab = "SDL_CORE")]
    [SemanticEntity(EntityName = "mymoduleArtikelPage")]
    [SemanticEntity(EntityName = "pageArtikelPage")]
    [SemanticEntity(EntityName = "mymodule:Page:ArtikelPage")]
    [SemanticEntity(EntityName = "regionSchema")]
    [SemanticEntity(EntityName = "ArtikelPage")]
    [SemanticEntity(EntityName = "metadata")]
    [SemanticEntity(EntityName = "Metadata")]
    [SemanticEntity(EntityName = "artikelPage")]
  
    public class CustomPageModel : PageModel
    {
        public CustomPageModel() : base() { }

        public CustomPageModel(string id) : base(id) { }

        [SemanticProperty("test1")]
        public string Test1 { get; set; }

        [SemanticProperty("test2")]
        public DateTime? Test2 { get; set; }

        [SemanticProperty("publishdate")]
        public DateTime? PublishDate { get; set; }

        //[SemanticProperty("metadata")]
        [SemanticProperty("paginaMetadata")]
        public PaginaMetadata PaginaMetadata { get; set; }
    }

The tab Source of the 'Artikel Page' Page schema.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="uuid:3feadbb7-8212-405d-9ac5-d212711e5a2f" xmlns:tcmi="http://www.tridion.com/ContentManager/5.0/Instance" elementFormDefault="qualified" targetNamespace="uuid:3feadbb7-8212-405d-9ac5-d212711e5a2f">
  <xsd:import namespace="http://www.tridion.com/ContentManager/5.0/Instance" schemaLocation="cm_xml_inst.xsd"></xsd:import>
  <xsd:include schemaLocation="tcm:105-418027-8"></xsd:include>
  <xsd:annotation>
    <xsd:appinfo>
      <tcm:Labels xmlns:tcm="http://www.tridion.com/ContentManager/5.0">
        <tcm:Label ElementName="test1" Metadata="true">Test1</tcm:Label>
        <tcm:Label ElementName="paginaMetadata" Metadata="true">Pagina metadata velden</tcm:Label>
        <tcm:Label ElementName="test2" Metadata="true">Test 2</tcm:Label>
      </tcm:Labels>
    </xsd:appinfo>
  </xsd:annotation>
  <xsd:element name="Metadata">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="test1" minOccurs="0" maxOccurs="1" type="xsd:normalizedString">
          <xsd:annotation>
            <xsd:appinfo>
              <ExtensionXml xmlns="http://www.tridion.com/ContentManager/5.0"></ExtensionXml>
            </xsd:appinfo>
          </xsd:annotation>
        </xsd:element>
        <xsd:element name="paginaMetadata" minOccurs="0" maxOccurs="1" type="PaginaMetadata">
          <xsd:annotation>
            <xsd:appinfo>
              <ExtensionXml xmlns="http://www.tridion.com/ContentManager/5.0"></ExtensionXml>
              <tcm:EmbeddedSchema xmlns:tcm="http://www.tridion.com/ContentManager/5.0" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="tcm:105-418027-8" xlink:title="PaginaMetadata"></tcm:EmbeddedSchema>
            </xsd:appinfo>
          </xsd:annotation>
        </xsd:element>
        <xsd:element name="test2" minOccurs="0" maxOccurs="1" type="xsd:dateTime">
          <xsd:annotation>
            <xsd:appinfo>
              <ExtensionXml xmlns="http://www.tridion.com/ContentManager/5.0"></ExtensionXml>
            </xsd:appinfo>
          </xsd:annotation>
        </xsd:element>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

enter image description here

UPDATE-2: BinaryData\tcm-99\system\mappings\schemas.json

    "Id": 417532,
    "RootElement": "ArtikelPage",
    "Fields": [
      {
        "Name": "test1",
        "Path": "/Metadata/test1",
        "IsMultiValue": false,
        "Semantics": [
          {
            "Prefix": "tri",
            "Entity": "ArtikelPage",
            "Property": "test1"
          }
        ],
        "Fields": [],
        "FieldType": 0
      },

Expected should work with value: [SemanticEntity(EntityName = "ArtikelPage")]

Workaround is 'workers' functions in the CustomPageModel and call when needed. More logical to the class instead of view(s) e.g..:

        public bool IsValidField(string field)
        {
            return this.Meta.ContainsKey(field) && this.Meta[field] != null;
        }
        public string GetFieldValue(string field)
        {  ...
        }

UPDATE-3 : It works. Added in tab 'Meta Design' of schema 'Article Page' this schema 'PaginaMetadata'. Its has Root Element name 'PaginaMetadata' and had to use it for the SemanticEntity. Props are filled now in CustomPageModel.

    [Serializable]
    [SemanticEntity(EntityName = "PaginaMetadata")]
  
    public class CustomPageModel : PageModel
    {
        public CustomPageModel() : base() { }

        public CustomPageModel(string id) : base(id) { }

        public DateTime? PublishDate { get; set; }

        public string Publisher { get; set; }
        ...
    }
5

You are correct in those two approaches, but there is something worth mentioning. For the 'Use this Page Schema as Metadata Schema' approach you don't have to use an Embeddable Schema, you could achieve the same thing by adding non-nested fields into the Region Schema (representing the Page Schema).

Both options comes in Razor view back as dictionary e.a. @Model.Meta["publishdate"] You have to check fields before usage.

True, the Page Model always has a Meta property in the form of a dictionary, which provides you with a non-strongly typed option.

Question-1: How to implement strong name for Metadata Schema?

See Custom Page Models. Now truth be told, the

You can map the metadata fields of the Page to Page Model properties by using the same model mapping mechanism used for Entities.

is not really helpful because (assuming Regular Mode (not Retrofit)) Entities rely on the Root Element Name of a Schema, whilst Metadata or Region (Page) Schemas don't have said property. For them, the correct way is described here.

Note the following:

The Title field (alphanumeric characters only) of the Schema, if the Schema is a Multimedia Schemas or a Metadata Schemas "

Now back to your questions:

Question-2: What is the way to go to collect metadata at page level?

There is no right approach, though I tend to use the custom Page Model method. It's more powerful and flexible.

  1. General (embedded) metadata schema is added to each page schema. Works OOTB.

What's the question here? As stated earlier, you don't need an Embedded Schema.

  1. Add metadata to components and extract that to page level. How to do that? OOTB?

Through Semantic Mapping as explained for Question-1. Same way like when you have a Component Link inside of your Entities

  1. Add metadata to Structure Group and get that in metadata with the pages. OOTB? Some field should be added for Page as metadata it self.

See the Model Builder Pipeline here. And the InheritMetadataPageModelBuilder here.

A model builder that adds metadata of the Structure Group containing the Page, or one of its parents, to the current Page

Now I haven't used it in a while, but IIRC, it behaves exactly the way you need to. If not, you can always create you own Model Builder and populate the Metadata on the CM side as needed.

UPDATE

It's exactly the same as for Entity Models, the only trick is to get the SemanticEntity Attribute correct. Example of a custom Page Model:

[SemanticEntity(EntityName = "TitleOfTheMetaOrPageSchemaNoSpacesSpecialCharactersRemoved")]
public class MyCustomPageModel : PageModel
{
    public MyCustomPageModel() : base() { }

    public MyCustomPageModel(string id) : base(id) { }

    [SemanticProperty("myCustomPropertyXmlName")]
    public string MyCustomProperty { get; set; }
}
10
  • Thanks for answers. Said it doesn't make sense to make custom page model. I am curious how to implement it in code, the reference to documentation doesn't say a lot. – Frits Lesterhuis Dec 25 '20 at 22:58
  • Updated the answer with an example – Atila Sos Dec 26 '20 at 11:26
  • Adding each fields in a "Page schema" tab "Metadata Design" is great. It easy to adjust input. Also fields in other "Page schema"can be made optional/mandatory input. Additional Embedded Schema can be also used. That works. – Frits Lesterhuis Dec 26 '20 at 16:12
  • 1
    Remove all SemanticEntity attributes from the model except one and try then (and don't use the one with the vocab) . If you use multiple, then you have to use prefixes, but before you go there, try and make it work for a single page type. – Atila Sos Dec 28 '20 at 15:21
  • 1
    Here’s an example of a custom Page Model (nice and clean :-): github.com/sdl/dxa-modules/blob/master/webapp-net/Test/Models/… – Rick Pannekoek Dec 29 '20 at 9:44
4

I see quite a bit of confusion around the difference between PageModel.Meta and custom Page Models w.r.t. mapping Page Metadata.

Indeed, PageModel.Meta can be used as “poor man’s semantic mapping”, but it is originally intended to be used to generate HTML meta tags in the rendered output.

By default, it contains some metadata for Facebook (OpenGraph) and Twitter, which is obtained from the Page and/or Components on the Page, next to simple representations of all Page metadata fields. This default is implemented in the DefaultPageMetaModelBuilder

Note that this is separate from the DefaultModelBuilder and it is possible to not use this DefaultPageMetaModelBuilder if you don’t intend to use the PageModel.Meta it generates or if you want to generate the PageModel.Meta differently.

If you want to use Page metadata in your Page View for different purposes than generating HTML meta tags, it is better to define a custom Page Model and use semantic mapping, as described by Atila.

1

I'll add a bit to address question 2: "What is the way to go to collect metadata at page level?"

General (embedded) metadata schema is added to each page schema. Works OOTB.

The metadata schema set at pages continues to work in Sites 9.5 for the classic UI, however in the (new) Experience Space UI, editors and their implementers are encouraged to use Page/Region metadata as configured for Page Templates going forward.

The idea is that editors can and should focus on the type of page they want to create by selecting the Page Template and this would in turn offer them the regions and "page" metadata they should use.

Going forward, implementers should opt for Regions or Region Metadata (set by editors using Page Templates) rather than the now older-style Page Metadata set for each page.

Add metadata to components and extract that to page level. How to do that?

This is a good approach for any search engine (webpage) metadata especially when the page has some kind of main or primary Component. Refer to the DXA example Rick provided, especially on the logic that checks for the HTML metadata from the Main Region.

This especially helps minimize the number of localizations especially for translation scenarios. Ideally, your editors would need to mostly translate Components rather than Pages, otherwise any (global) page localizations lose control over the Component Presentations on those pages (and additional translations need to be sent for each language).

Add metadata to Structure Group and get that in metadata with the pages. Some field should be added for Page as metadata it self.

This is my preferred approach for any setting or content that should apply to entire sections of a site. The idea is that settings in Structure Group metadata would, by default, apply to the contained Structure Groups and Pages. Editors could then choose "override" a setting in a given page.

Similar "specificity" patterns are seen in programmatic scoping rules/inheritance as well as in BluePrinting itself, in folder permission settings, dynamic linking proximity rules, and when content modeling things like alternative text (use the alt text set in an image unless you provide some kind of override).

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.