HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Conventions API

Get the ConventionRepository singleton instance to configure conventions.

📘

Note

Perform this configuration only once during the site's startup, because modifying conventions while the site is running can cause unpredictable behavior.

For example, create a scope in startup.cs to retrieve the ConventionRepository and then configure it. Create a temporary scope in startup.cs to resolve the singleton.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();
  conventionRepo.ForInstancesOf < StandardPage > ()
    .ExcludeField(p => p.ContentAreaItem1) //exclude a field
    .IncludeField(p => p.TermsAndConditions()) //add a dynamic field
    //set indexing type for specific properties
    .Set(p => p.Price, IndexingType.Queryable)
    .Set(p => p.Heading, IndexingType.Searchable)
    .Set(p => p.Quantity, IndexingType.OnlyStored);

  conventionRepo.ExcludeContentType < StartPage > (); //exclude specific content types

  conventionRepo.ExcludeAllContentTypes() //exclude all content types except a few
    .Except < OrderBlock > ();

  conventionRepo
    .IncludeAbstract < MyAbstractPage2 > () //add interface and abstract types to schema
    .IncludeAbstract < MyAbstractPage > ()
    .IncludeInterface < ISearchPage > ();
}

📘

Note

Content types and content data are not resynced automatically when conventions are changed, and the site is restarted. After changing conventions in code and redeploying the site, you should re-run the Optimizely Graph synchronization job so data is consistent with your configurations. Not doing so will result in unpredictable outcomes.

Exclude content types

You can exclude content types from being synced to Optimizely Graph schema, and contents of the excluded content types will not be synced from Optimizely Graph.

Example: exclude StandardPage and OrderBlock.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo.ExcludeContentType < StandardPage > ()
    .ExcludeContentType < OrderBlock > ();
}

When this happens StandardPage and OrderBlock will not be included in the GraphQL schema and there will not be contents of type StandardPage and OrderBlock synced to Graph..

When you add contents of excluded content types ContentArea or ContentReference and other reference fields of another type (such as adding a StandardPage to a ContentArea field of a StartPage), the contents of excluded content types will not be included in the ContentArea.

For excluded block types, the block properties of that type are still available in the content, because the block properties are part of the content. If the blocks are created as standalone contents, they are excluded from synchronization to Optimizely Graph.

Exclude all content types except some

The following code excludes all content types except for some content types as an alternative way to configure.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo.ExcludeAllContentTypes()
    .Except < StandardPage > ()
    .Except < OrderBlock > ();
}

Exclude fields

You can exclude fields from content types. When a field is excluded, it is not synced to Optimizely Graph and is not included in the GraphQL schema. The following example configures an Optimizely Graph integration to exclude ContentAreaItem1 and MainBody properties from StandardPage content type.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo.ForInstancesOf < StandardPage > ()
    .ExcludeField(p => p.ContentAreaItem1)
    .ExcludeField(p => p.MainBody);
}

Set indexing type for fields

Use the ConventionRepository to set the indexing type for code fields. This method overrides settings from the CMS UI, the [Searchable] attribute, and Optimizely Graph's PropertyIndexingType attributes.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo.ForInstancesOf < StandardPage > ()
    .Set(page => page.Keywords, IndexingType.Searchable)
    .Set(page => page.Price, IndexingType.Queryable)
    .Set(page => page.Quantity, IndexingType.OnlyStored);
}

There are three IndexingTypes when configuring using Conventions API.

  • Searchable – The property is searchable using full-text search. Searchable is only applicable to leaf properties of primitive types (string, int, datetime, and so on).
  • Queryable – The property can be filtered using operators like eq, gt, gte, but not searchable when doing full-text search.
  • OnlyStored – The property can be projected in GraphQL, but it is not searchable nor filterable.

Include fields

Conventions API lets you add calculated fields to the content when synced to Optimizely Graph. You can implement the field as a method on the ContentType class.

The following code shows an example with a block type called OrderBlock.

[ContentType]
public class OrderBlock: BlockData {
  public virtual string OrderDetails {
    get;
    set;
  }
  public virtual int Quantity {
    get;
    set;
  }

  public virtual double Price {
    get;
    set;
  }

  public double TotalPrice() {
    return Quantity * Price;
  }
}

In this example, in CMS the OrderBlock content type has properties: OrderDetails, Quantity, and Price.

The method TotalPrice calculates the total price by multiplying Quantity and Price.

You can add TotalPrice as a calculated field when synchronizing OrderBlock contents by configuring this convention in startup.cs.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo.ForInstancesOf < OrderBlock > ()
    .IncludeField(p => p.TotalPrice());

}

Then, when an OrderBlock is created (as shown)...

...and then synced to Optimizely Graph, you can query for TotalPrice value.

📘

NOTE

Dynamic fields use lambdas in IncludeField, which may not support all expression variations. Because calculated fields are only in Optimizely Graph, implement on-page editing to view them in the CMS editor by showing them in the frontend site's preview.

📘

WARNING

You should use simple methods like in the example that run quickly to avoid issues when synchronizing contents to Optimizely Graph.

Use IncludeField to add any form of data to the content item that does not belong to CMS Content. Do not make the method return CMS data like ContentReference, ContentArea, Blob, or Blocks because they will not work like real CMS properties and many features associated with those data types will not work.

IncludeField will not allow adding methods that return the following CMS types. You should not use the following CMS data types with IncludeField.

  • Blob
  • ContentArea
  • ContentAreaItem
  • ContentReference
  • LinkItem
  • LinkItemCollection
  • PageData
  • BlockData
  • MediaData
  • XhtmlString

Example of multiple language support with calculated fields

You can give calculated fields specific values for different languages. The following code adds a field that returns the TermsAndConditions link.

[ContentType]
public class StandardPage: MyAbstractPage2, ISearchPage {
  //... other properties

  public string TermsAndConditions() {
    var language = this.Language.Name;
    var tncLink = "https://ourcorp/tnc";
    var tnc = "Terms and conditions";
    switch (language) {
    case "sv":
      tnc = "villkor";
      break;
    case "vi":
      tnc = "Điều khoản và điều kiện";
      break;
    }
    return $ "{tnc}: {tncLink}";
  }
}

The method can return different texts depending on the current language, the default text is in English.

The following code configures to include this TermsAndConditions field.

conventionRepo.ForInstancesOf < StandardPage > ()
  .IncludeField(p => p.TermsAndConditions());

When a StandardPage has multiple versions in different languages, the TermsAndConditions field will change accordingly.

Include abstract types in GraphQL

When creating content types from code in CMS 12, you can use abstract classes as base classes for content types. Abstract classes are not added to GraphQL schema by default. You can use Conventions API to add the abstract classes to Optimizely Graph, then you can query contents by both the content type and the abstract types.

The following example shows when StandardPage is extended from MyAbstractPage2, then MyAbstractPage2 is extended from MyAbstractPage.

public abstract class MyAbstractPage: PageData {
  [Display(
    GroupName = SystemTabNames.Content,
    Order = 310)]
  [CultureSpecific]
  public virtual XhtmlString MainBody {
    get;
    set;
  }
}

public abstract class MyAbstractPage2: MyAbstractPage {
  [Display(
    GroupName = SystemTabNames.Content,
    Order = 310)]
  [CultureSpecific]
  public virtual XhtmlString MainBody2 {
    get;
    set;
  }
}

[ContentType(GUID = "9CCC8A41-5C8C-4BE0-8E73-520FF3DE8267")]
public class StandardPage: MyAbstractPage2 {
  //Standard Page fields.
}

You can configure to include MyAbstractPage2 and MyAbstractPage using conventions API

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService < ConventionRepository > ();

  conventionRepo
    .IncludeAbstract < MyAbstractPage > ()
    .IncludeAbstract < MyAbstractPage2 > ();
}

After running the sync job, the abstract types are now listed in the schema.

You can query for standard page using either StandardPage, MyAbstractPage, or MyAbstractPage2.

When using abstract types, you can use inline fragments to cast the data to other types in the inheritance tree because, in GraphQL, these types are also marked as abstract. This works because abstract classes have all fields of more specific classes, so it is supported by GraphQL.

Include interface types in GraphQL

You can include interface types in the GraphQL schema, aside from abstract types. Given that StandardPage also implements ISearchPage interface, see the following code.

public interface ISearchPage {
  string Keywords {
    get;
    set;
  }
}

[ContentType(GUID = "9CCC8A41-5C8C-4BE0-8E73-520FF3DE8267")]
public class StandardPage: MyAbstractPage2, ISearchPage {
  //Standard Page fields.
}

Use IncludeInterface<T> to add the interface to GraphQL.

using(var serviceScope = app.ApplicationServices.CreateScope()) {
  var services = serviceScope.ServiceProvider;
  var conventionRepo = services.GetRequiredService<ConventionRepository>();

  conventionRepo.IncludeInterface<ISearchPage>();
}

After contents are synced, you can also query using the interface type.

📘

Note

Interface types are not abstract types, so you cannot cast it to other types like MyAbstractPage2, MyAbstractPage, and StandardPage. This is because interface can have fields that are not in some classes in the chain of inheritance, so making interface an abstract type will cause validation errors from GraphQL when both MyAbstractPage and ISearchPage are queried.