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 likeContentReference
,ContentArea
,Blob
, orBlocks
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 withIncludeField
.
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 likeMyAbstractPage2
,MyAbstractPage
, andStandardPage
. This is becauseinterface
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 bothMyAbstractPage
andISearchPage
are queried.
Updated about 1 month ago