diff --git a/10/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md b/10/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md index 77299f7f1fc..12d2dc28c24 100644 --- a/10/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md +++ b/10/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md @@ -1,21 +1,22 @@ --- +description: Understanding and Extending ModelsBuilder in Umbraco +--- -meta.Title: "Understand and Extend Modelsbuilder" -description: "Understand and extend modelsbuilder" ---- +# Introduction +Umbraco’s Models Builder automatically generates strongly typed models for content types, allowing developers to work with Umbraco data in a structured and efficient manner. This article explains how models are generated, how composition and inheritance work, and best practices for extending models without causing issues. -# Understand and Extend +## Models Generation Process -Models are generated as partial classes. In its most basic form, a model for content type `TextPage` ends up in a `TextPage.generated.cs` file and looks like: +Models Builder generates each content type as a partial class. For example, a content type named `TextPage` results in a `TextPage.generated.cs` file with a structure like this: ```csharp /// TextPage [PublishedModel("textPage")] public partial class TextPage : PublishedContentModel { - // helpers + //static helpers public new const string ModelTypeAlias = "textPage"; public new const PublishedItemType ModelItemType = PublishedItemType.Content; @@ -28,7 +29,7 @@ public partial class TextPage : PublishedContentModel private IPublishedValueFallback _publishedValueFallback; - // ctor + //constructor public TextPage(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) { @@ -45,7 +46,12 @@ public partial class TextPage : PublishedContentModel } ``` -What is important is the `Header` property. The rest is (a) a constructor and (b) some static helpers to get the `PublishedContentType` and the `PublishedPropertyType` objects: +In the above code: + +* The model includes a constructor and static helpers to fetch the content type (`PublishedContentType`) and property type (`PublishedPropertyType`). +* The most important part is the property definition (`Header`), which retrieve values from Umbraco. + +You can use helper methods to access content and property types: ```csharp var contentType = TextPage.GetModelContentType(); // is a PublishedContentType @@ -54,11 +60,16 @@ var propertyType = TextPage.GetModelPropertyType(x => x.Header); // is a Publish ## Composition and Inheritance -Content type *composition* consists in having content types "inherit" properties from other content types. Contrary to C#, where a class can only inherit from one other class, Umbraco content types can be composed of several other content types. +### Composition + +Umbraco content types can be composed of multiple other content types. Unlike traditional C# inheritance, Umbraco allows a content type to inherit properties from multiple sources. -The `TextPage` content type could be composed of the `MetaInfo` content type (and thus inherit properties `Author` and `Keywords`) and of the `PageInfo` content type (and thus inherit properties `Title` and `MainImage`). +For example, a `TextPage` might be composed of: -Each content type that is involved in a composition is generated both as a class and as an interface, and so the `MetaInfo` content type would be generated as (some code has been removed and altered for simplicity's sake): +* **MetaInfo** content type (inherits `Author` and `Keywords` properties). +* **PageInfo** content type (inherits `Title` and `MainImage` properties). + +Each content type in a composition is generated both as a class and as an interface. The `MetaInfo` content type would be generated as: ```csharp // The composition interface @@ -81,7 +92,7 @@ public partial class MetaInfo : PublishedContentModel } ``` -And the `TextPage` model would be generated as (some code has been removed and altered for simplicity's sake): +And the `TextPage` model would be generated as: ```csharp public partial class TextPage : PublishedContentModel, IMetaInfo @@ -91,9 +102,13 @@ public partial class TextPage : PublishedContentModel, IMetaInfo } ``` -A content type *parent* is a tree-related concept: In the Umbraco backoffice, a content type appears underneath its parent, if any. By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. +### Inheritance + +In addition to composition, content types can have a parent-child relationship. In the Umbraco backoffice, a content type appears underneath its parent. -Therefore, assuming that the `AboutPage` content type is a direct child of `TextPage`, it would be generated as: +By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. + +If `AboutPage` is a child of TextPage, its generated model would inherit directly from `TextPage`: ```csharp // Note: Inherits from TextPage @@ -103,9 +118,11 @@ public partial class AboutPage : TextPage } ``` -## Extending +## Extending Models + +Since models are partial classes, developers can extend them by adding additional properties. -Because a model is generated as a partial class, it is possible to extend it. That could be by adding a property, by dropping the following code in a `TextPage.cs` file: +For Example: ```csharp public partial class TextPage @@ -114,17 +131,17 @@ public partial class TextPage } ``` -Models builder does not take a custom partial class into account when generating the models. This means that if a custom partial class, inherits from a base class, tries to provide a constructor with the same signature, or implements a generated property, it will cause compilation errors. +Models Builder does not recognize custom partial classes during regeneration. If your custom class conflicts with the generated class (e.g., overriding a constructor), it will cause compilation errors. -Furthermore a generated model will always be instantiated with its default constructor, so if an overloading constructor is created it will never be used. +Overloaded constructors will not be used because models are always instantiated using the default constructor. -For more complex partial classes, you'll have to use the full version of the [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). +For more complex customizations, use the full version of [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). -## Best Practices +## Best Practices for Extending Models -Extending models should be used to add stateless, local features to models, and *not* to transform *content* models into view models or manage trees of content. +Extending models should be used to add stateless, local features to models. It should not be used to transform *content* models into view models or manage trees of content. -### Example of good practice +### Good practices A customer has "posts" that has two "release date" properties. One is a true date picker property and is used to specify an actual date and to order the posts. The other is a string that is used to specify dates such as "Summer 2015" or "Q1 2016". Alongside the title of the post, the customer wants to display the text date, if present, else the actual date. If none of those are present, the Umbraco update date should be used. Keep in mind that each view can contain code to deal with the situation, but it is much more efficient to extend the `Post` model: @@ -151,7 +168,7 @@ A customer has "posts" that has two "release date" properties. One is a true dat } ``` -And to simplify the view as: +Simplified view: ```csharp
@@ -160,13 +177,12 @@ And to simplify the view as:
``` -### Example of bad practice +### Bad practices Because, by default, the content object is passed to views, one can be tempted to add view-related properties to the model. Some properties that do *not* belong to a *content* model would be: -* A `HomePage` property that would walk up the tree and return the "home page" content item -* A `Menu` property that would list the content items to display in a top menu -* Etc. +* A `HomePage` property that retrieves the "home page" content item. +* A `Menu` property that lists navigation items. Generally speaking, anything that is tied to the current request, or that depends on more than the modeled content, is a bad idea. There are much cleaner solutions, such as using true *view model* classes that would be populated by a true controller and look like: @@ -179,17 +195,17 @@ public class TextPageViewModel } ``` -One can also extend Umbraco's views to provide a special view helper that would give access to important elements of the website, so that views could contain code such as: +One can also extend Umbraco's views to provide a special view helper that gives access to important elements of the website: ```csharp @MySite.HomePage.Title ``` -### Example of ugly practice +### Ugly practices -The scope and life-cycle of a model is *not specified*. In other words, you don't know whether the model exists only for you and for the context of the current request, or if it is cached by Umbraco and shared by all requests. +The model's scope and lifecycle are *unspecified*. It may exist only for your request or be cached and shared across all requests. -As a consequence, the following code has a major issue: the `TextPage` model "caches" an instance of the `HomePageDocument` model that will never be updated if the home page is re-published. +The code has a major issue: the `TextPage` model caches a `HomePageDocument` model that will not update when the home page is re-published. ```csharp private HomePageDocument _homePage; @@ -206,4 +222,4 @@ public HomePageDocument HomePage } ``` -As a rule of thumb, models should never, *ever* reference and cache other models. +As a rule of thumb, models should never reference and cache other models. diff --git a/13/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md b/13/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md index ff55fbf8024..096a3c0d214 100644 --- a/13/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md +++ b/13/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md @@ -1,18 +1,22 @@ --- -description: "Understand and extend modelsbuilder" +description: Understanding and Extending ModelsBuilder in Umbraco --- -# Understand and Extend +# Introduction -Models are generated as partial classes. In its most basic form, a model for content type `TextPage` ends up in a `TextPage.generated.cs` file and looks like: +Umbraco’s Models Builder automatically generates strongly typed models for content types, allowing developers to work with Umbraco data in a structured and efficient manner. This article explains how models are generated, how composition and inheritance work, and best practices for extending models without causing issues. + +## Models Generation Process + +Models Builder generates each content type as a partial class. For example, a content type named `TextPage` results in a `TextPage.generated.cs` file with a structure like this: ```csharp /// TextPage [PublishedModel("textPage")] public partial class TextPage : PublishedContentModel { - // helpers + //static helpers public new const string ModelTypeAlias = "textPage"; public new const PublishedItemType ModelItemType = PublishedItemType.Content; @@ -25,7 +29,7 @@ public partial class TextPage : PublishedContentModel private IPublishedValueFallback _publishedValueFallback; - // ctor + //constructor public TextPage(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) { @@ -42,22 +46,30 @@ public partial class TextPage : PublishedContentModel } ``` -What is important is the `Header` property. The rest is (a) a constructor and (b) some static helpers to get the `PublishedContentType` and the `PublishedPropertyType` objects: +In the above code: + +* The model includes a constructor and static helpers to fetch the content type (`PublishedContentType`) and property type (`PublishedPropertyType`). +* The most important part is the property definition (`Header`), which retrieve values from Umbraco. + +You can use helper methods to access content and property types: ```csharp var contentType = TextPage.GetModelContentType(_publishedSnapshotAccessor); // is a PublishedContentType var propertyType = TextPage.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Header); // is a PublishedPropertyType - -// Where _publishedSnapshotAccessor is an injected IPublishedSnapshotAccessor service ``` ## Composition and Inheritance -Content type *composition* consists in having content types "inherit" properties from other content types. Contrary to C#, where a class can only inherit from one other class, Umbraco content types can be composed of several other content types. +### Composition -The `TextPage` content type could be composed of the `MetaInfo` content type (and thus inherit properties `Author` and `Keywords`) and of the `PageInfo` content type (and thus inherit properties `Title` and `MainImage`). +Umbraco content types can be composed of multiple other content types. Unlike traditional C# inheritance, Umbraco allows a content type to inherit properties from multiple sources. -Each content type that is involved in a composition is generated both as a class and as an interface, and so the `MetaInfo` content type would be generated as (some code has been removed and altered for simplicity's sake): +For example, a `TextPage` might be composed of: + +* **MetaInfo** content type (inherits `Author` and `Keywords` properties). +* **PageInfo** content type (inherits `Title` and `MainImage` properties). + +Each content type in a composition is generated both as a class and as an interface. The `MetaInfo` content type would be generated as: ```csharp // The composition interface @@ -80,7 +92,7 @@ public partial class MetaInfo : PublishedContentModel } ``` -And the `TextPage` model would be generated as (some code has been removed and altered for simplicity's sake): +And the `TextPage` model would be generated as: ```csharp public partial class TextPage : PublishedContentModel, IMetaInfo @@ -90,9 +102,13 @@ public partial class TextPage : PublishedContentModel, IMetaInfo } ``` -A content type *parent* is a tree-related concept: In the Umbraco backoffice, a content type appears underneath its parent, if any. By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. +### Inheritance + +In addition to composition, content types can have a parent-child relationship. In the Umbraco backoffice, a content type appears underneath its parent. -Therefore, assuming that the `AboutPage` content type is a direct child of `TextPage`, it would be generated as: +By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. + +If `AboutPage` is a child of TextPage, its generated model would inherit directly from `TextPage`: ```csharp // Note: Inherits from TextPage @@ -102,9 +118,11 @@ public partial class AboutPage : TextPage } ``` -## Extending +## Extending Models + +Since models are partial classes, developers can extend them by adding additional properties. -Because a model is generated as a partial class, it is possible to extend it. That could be by adding a property, by dropping the following code in a `TextPage.cs` file: +For Example: ```csharp public partial class TextPage @@ -113,23 +131,23 @@ public partial class TextPage } ``` -Models builder does not take a custom partial class into account when generating the models. This means that if a custom partial class, inherits from a base class, tries to provide a constructor with the same signature, or implements a generated property, it will cause compilation errors. +Models Builder does not recognize custom partial classes during regeneration. If your custom class conflicts with the generated class (e.g., overriding a constructor), it will cause compilation errors. -Furthermore a generated model will always be instantiated with its default constructor, so if an overloading constructor is created it will never be used. +Overloaded constructors will not be used because models are always instantiated using the default constructor. -For more complex partial classes, you'll have to use the full version of the [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). +For more complex customizations, use the full version of [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). -### IModelsGenerator +### Custom Model Generation with IModelsGenerator -As of Umbraco 11.4, the IModelsGenerator interface has been added. If you want to customize how the models are generated, you can make your own implementation of the `IModelsGenerator` interface. You can then overwrite the Umbraco implementation with dependency injection. +From Umbraco 11.4, you can implement the `IModelsGenerator` interface hto customize how models are generated. This allows you to replace Umbraco’s default implementation using dependency injection: The interface can be accessed via `Infrastructure.ModelsBuilder.Building.ModelsGenerator`. -## Best Practices +## Best Practices for Extending Models Extending models should be used to add stateless, local features to models. It should not be used to transform *content* models into view models or manage trees of content. -### Example of good practice +### Good practices A customer has "posts" that has two "release date" properties. One is a true date picker property and is used to specify an actual date and to order the posts. The other is a string that is used to specify dates such as "Summer 2015" or "Q1 2016". Alongside the title of the post, the customer wants to display the text date, if present, else the actual date. If none of those are present, the Umbraco update date should be used. Keep in mind that each view can contain code to deal with the situation, but it is much more efficient to extend the `Post` model: @@ -156,7 +174,7 @@ A customer has "posts" that has two "release date" properties. One is a true dat } ``` -And to simplify the view as: +Simplified view: ```csharp
@@ -165,13 +183,12 @@ And to simplify the view as:
``` -### Example of bad practice +### Bad practices Because, by default, the content object is passed to views, one can be tempted to add view-related properties to the model. Some properties that do *not* belong to a *content* model would be: -* A `HomePage` property that would walk up the tree and return the "home page" content item -* A `Menu` property that would list the content items to display in a top menu -* Etc. +* A `HomePage` property that retrieves the "home page" content item. +* A `Menu` property that lists navigation items. Generally speaking, anything that is tied to the current request, or that depends on more than the modeled content, is a bad idea. There are much cleaner solutions, such as using true *view model* classes that would be populated by a true controller and look like: @@ -184,17 +201,17 @@ public class TextPageViewModel } ``` -One can also extend Umbraco's views to provide a special view helper that would give access to important elements of the website, so that views could contain code such as: +One can also extend Umbraco's views to provide a special view helper that gives access to important elements of the website: ```csharp @MySite.HomePage.Title ``` -### Example of ugly practice +### Ugly practices -The scope and life-cycle of a model is *not specified*. In other words, you don't know whether the model exists only for you and for the context of the current request, or if it is cached by Umbraco and shared by all requests. +The model's scope and lifecycle are *unspecified*. It may exist only for your request or be cached and shared across all requests. -As a consequence, the following code has a major issue: the `TextPage` model "caches" an instance of the `HomePageDocument` model that will never be updated if the home page is re-published. +The code has a major issue: the `TextPage` model caches a `HomePageDocument` model that will not update when the home page is re-published. ```csharp private HomePageDocument _homePage; @@ -211,4 +228,4 @@ public HomePageDocument HomePage } ``` -As a rule of thumb, models should never, *ever* reference and cache other models. +As a rule of thumb, models should never reference and cache other models. diff --git a/14/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md b/14/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md index a4aa0928e1d..a77665b3952 100644 --- a/14/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md +++ b/14/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md @@ -1,18 +1,22 @@ --- -description: "Understand and extend modelsbuilder" +description: Understanding and Extending ModelsBuilder in Umbraco --- -# Understand and Extend +# Introduction -Models are generated as partial classes. In its most basic form, a model for content type `TextPage` ends up in a `TextPage.generated.cs` file and looks like: +Umbraco’s Models Builder automatically generates strongly typed models for content types, allowing developers to work with Umbraco data in a structured and efficient manner. This article explains how models are generated, how composition and inheritance work, and best practices for extending models without causing issues. + +## Models Generation Process + +Models Builder generates each content type as a partial class. For example, a content type named `TextPage` results in a `TextPage.generated.cs` file with a structure like this: ```csharp /// TextPage [PublishedModel("textPage")] public partial class TextPage : PublishedContentModel { - // helpers + //static helpers public new const string ModelTypeAlias = "textPage"; public new const PublishedItemType ModelItemType = PublishedItemType.Content; @@ -25,7 +29,7 @@ public partial class TextPage : PublishedContentModel private IPublishedValueFallback _publishedValueFallback; - // ctor + //constructor public TextPage(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) { @@ -42,24 +46,34 @@ public partial class TextPage : PublishedContentModel } ``` -What is important is the `Header` property. The rest is (a) a constructor and (b) some static helpers to get the `PublishedContentType` and the `PublishedPropertyType` objects: +In the above code: + +* The model includes a constructor and static helpers to fetch the content type (`PublishedContentType`) and property type (`PublishedPropertyType`). +* The most important part is the property definition (`Header`), which retrieve values from Umbraco. + +You can use helper methods to access content and property types: ```csharp var contentType = TextPage.GetModelContentType(); // is a PublishedContentType var propertyType = TextPage.GetModelPropertyType(x => x.Header); // is a PublishedPropertyType ``` -## Compositions +## Composition and Inheritance -Content type *Compositions* in Umbraco allows content types to *inherit* properties from multiple other content types. Unlike C#, where a class can only inherit from one other class, Umbraco content types can be composed of multiple other content types. +### Composition + +Umbraco content types can be composed of multiple other content types. Unlike traditional C# inheritance, Umbraco allows a content type to inherit properties from multiple sources. {% hint style="info" %} In Umbraco v14, the traditional .NET Inheritance feature has been removed. Instead, properties are inherited through Composition, allowing for greater flexibility in managing content types. {% endhint %} -For example, `TextPage` content type could be composed of `MetaInfo` (inheriting properties such as `Author` and `Keywords`) and `PageInfo` (inheriting `Title` and `MainImage`) content types. +For example, a `TextPage` might be composed of: + +* **MetaInfo** content type (inherits `Author` and `Keywords` properties). +* **PageInfo** content type (inherits `Title` and `MainImage` properties). -Each content type involved in a composition is generated both as a class and as an interface. Thus, the `MetaInfo` content type would be generated as follows (some code has been removed and altered for simplicity's sake): +Each content type involved in a composition is generated both as a class and as an interface. The `MetaInfo` content type would be generated as: ```csharp // The composition interface @@ -82,7 +96,7 @@ public partial class MetaInfo : PublishedContentModel } ``` -And the `TextPage` model would be generated as follows (some code has been removed and altered for simplicity's sake): +And the `TextPage` model would be generated as: ```csharp public partial class TextPage : PublishedContentModel, IMetaInfo @@ -92,11 +106,13 @@ public partial class TextPage : PublishedContentModel, IMetaInfo } ``` -In the Umbraco Backoffice, a content type can appear underneath its parent content type in the content tree. +### Inheritance -By convention, a content type inherits properties from its parent in a compositional manner. However, this does not use traditional class inheritance but rather a compositional approach. This approach ensures content types can combine multiple sets of properties without the limitations of single inheritance. +In addition to composition, content types can have a parent-child relationship. In the Umbraco backoffice, a content type appears underneath its parent. -Therefore, assuming that the `AboutPage` content type is a direct child of `TextPage`, it would be generated as: +By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. + +If `AboutPage` is a child of TextPage, its generated model would inherit directly from `TextPage`: ```csharp // Note: Inherits from TextPage @@ -106,9 +122,11 @@ public partial class AboutPage : TextPage } ``` -## Extending +## Extending Models + +Since models are partial classes, developers can extend them by adding additional properties. -Because a model is generated as a partial class, it is possible to extend it. That could be by adding a property, by dropping the following code in a `TextPage.cs` file: +For Example: ```csharp public partial class TextPage @@ -117,23 +135,23 @@ public partial class TextPage } ``` -Models builder does not take a custom partial class into account when generating the models. This means that if a custom partial class, inherits from a base class, tries to provide a constructor with the same signature, or implements a generated property, it will cause compilation errors. +Models Builder does not recognize custom partial classes during regeneration. If your custom class conflicts with the generated class (e.g., overriding a constructor), it will cause compilation errors. -Furthermore a generated model will always be instantiated with its default constructor, so if an overloading constructor is created it will never be used. +Overloaded constructors will not be used because models are always instantiated using the default constructor. -For more complex partial classes, you'll have to use the full version of the [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). +For more complex customizations, use the full version of [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). -### IModelsGenerator +### Custom Model Generation with IModelsGenerator -As of Umbraco 11.4, the IModelsGenerator interface has been added. If you want to customize how the models are generated, you can make your own implementation of the `IModelsGenerator` interface. You can then overwrite the Umbraco implementation with dependency injection. +From Umbraco 11.4, you can implement the `IModelsGenerator` interface hto customize how models are generated. This allows you to replace Umbraco’s default implementation using dependency injection: The interface can be accessed via `Infrastructure.ModelsBuilder.Building.ModelsGenerator`. -## Best Practices +## Best Practices for Extending Models Extending models should be used to add stateless, local features to models. It should not be used to transform *content* models into view models or manage trees of content. -### Example of good practice +### Good practices A customer has "posts" that has two "release date" properties. One is a true date picker property and is used to specify an actual date and to order the posts. The other is a string that is used to specify dates such as "Summer 2015" or "Q1 2016". Alongside the title of the post, the customer wants to display the text date, if present, else the actual date. If none of those are present, the Umbraco update date should be used. Keep in mind that each view can contain code to deal with the situation, but it is much more efficient to extend the `Post` model: @@ -160,7 +178,7 @@ A customer has "posts" that has two "release date" properties. One is a true dat } ``` -And to simplify the view as: +Simplified view: ```csharp
@@ -169,13 +187,12 @@ And to simplify the view as:
``` -### Example of bad practice +### Bad practices Because, by default, the content object is passed to views, one can be tempted to add view-related properties to the model. Some properties that do *not* belong to a *content* model would be: -* A `HomePage` property that would walk up the tree and return the "home page" content item -* A `Menu` property that would list the content items to display in a top menu -* Etc. +* A `HomePage` property that retrieves the "home page" content item. +* A `Menu` property that lists navigation items. Generally speaking, anything that is tied to the current request, or that depends on more than the modeled content, is a bad idea. There are much cleaner solutions, such as using true *view model* classes that would be populated by a true controller and look like: @@ -188,17 +205,17 @@ public class TextPageViewModel } ``` -One can also extend Umbraco's views to provide a special view helper that would give access to important elements of the website, so that views could contain code such as: +One can also extend Umbraco's views to provide a special view helper that gives access to important elements of the website: ```csharp @MySite.HomePage.Title ``` -### Example of ugly practice +### Ugly practices -The scope and life-cycle of a model is *not specified*. In other words, you don't know whether the model exists only for you and for the context of the current request, or if it is cached by Umbraco and shared by all requests. +The model's scope and lifecycle are *unspecified*. It may exist only for your request or be cached and shared across all requests. -As a consequence, the following code has a major issue: the `TextPage` model "caches" an instance of the `HomePageDocument` model that will never be updated if the home page is re-published. +The code has a major issue: the `TextPage` model caches a `HomePageDocument` model that will not update when the home page is re-published. ```csharp private HomePageDocument _homePage; @@ -215,4 +232,4 @@ public HomePageDocument HomePage } ``` -As a rule of thumb, models should never, *ever* reference and cache other models. +As a rule of thumb, models should never reference and cache other models. diff --git a/15/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md b/15/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md index a968418d316..c3d7b89d21e 100644 --- a/15/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md +++ b/15/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md @@ -1,11 +1,15 @@ --- -description: "Understand and extend modelsbuilder" +description: Understanding and Extending ModelsBuilder in Umbraco --- -# Understand and Extend +# Introduction -Models are generated as partial classes. In its most basic form, a model for content type `TextPage` ends up in a `TextPage.generated.cs` file and looks like: +Umbraco’s Models Builder automatically generates strongly typed models for content types, allowing developers to work with Umbraco data in a structured and efficient manner. This article explains how models are generated, how composition and inheritance work, and best practices for extending models without causing issues. + +## Models Generation Process + +Models Builder generates each content type as a partial class. For example, a content type named `TextPage` results in a `TextPage.generated.cs` file with a structure like this: {% include "../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} @@ -14,7 +18,7 @@ Models are generated as partial classes. In its most basic form, a model for con [PublishedModel("textPage")] public partial class TextPage : PublishedContentModel { - // helpers + //static helpers public new const string ModelTypeAlias = "textPage"; public new const PublishedItemType ModelItemType = PublishedItemType.Content; @@ -27,7 +31,7 @@ public partial class TextPage : PublishedContentModel private IPublishedValueFallback _publishedValueFallback; - // ctor + //constructor public TextPage(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) { @@ -44,24 +48,34 @@ public partial class TextPage : PublishedContentModel } ``` -What is important is the `Header` property. The rest is (a) a constructor and (b) some static helpers to get the `PublishedContentType` and the `PublishedPropertyType` objects: +In the above code: + +* The model includes a constructor and static helpers to fetch the content type (`PublishedContentType`) and property type (`PublishedPropertyType`). +* The most important part is the property definition (`Header`), which retrieve values from Umbraco. + +You can use helper methods to access content and property types: ```csharp var contentType = TextPage.GetModelContentType(); // is a PublishedContentType var propertyType = TextPage.GetModelPropertyType(x => x.Header); // is a PublishedPropertyType ``` -## Compositions +## Composition and Inheritance -Content type *Compositions* in Umbraco allows content types to *inherit* properties from multiple other content types. Unlike C#, where a class can only inherit from one other class, Umbraco content types can be composed of multiple other content types. +### Composition + +Umbraco content types can be composed of multiple other content types. Unlike traditional C# inheritance, Umbraco allows a content type to inherit properties from multiple sources. {% hint style="info" %} In Umbraco v14, the traditional .NET Inheritance feature has been removed. Instead, properties are inherited through Composition, allowing for greater flexibility in managing content types. {% endhint %} -For example, `TextPage` content type could be composed of `MetaInfo` (inheriting properties such as `Author` and `Keywords`) and `PageInfo` (inheriting `Title` and `MainImage`) content types. +For example, a `TextPage` might be composed of: + +* **MetaInfo** content type (inherits `Author` and `Keywords` properties). +* **PageInfo** content type (inherits `Title` and `MainImage` properties). -Each content type involved in a composition is generated both as a class and as an interface. Thus, the `MetaInfo` content type would be generated as follows (some code has been removed and altered for simplicity's sake): +Each content type in a composition is generated both as a class and as an interface. The `MetaInfo` content type would be generated as: ```csharp // The composition interface @@ -84,7 +98,7 @@ public partial class MetaInfo : PublishedContentModel } ``` -And the `TextPage` model would be generated as follows (some code has been removed and altered for simplicity's sake): +And the `TextPage` model would be generated as: ```csharp public partial class TextPage : PublishedContentModel, IMetaInfo @@ -94,11 +108,13 @@ public partial class TextPage : PublishedContentModel, IMetaInfo } ``` -In the Umbraco Backoffice, a content type can appear underneath its parent content type in the content tree. +### Inheritance -By convention, a content type inherits properties from its parent in a compositional manner. However, this does not use traditional class inheritance but rather a compositional approach. This approach ensures content types can combine multiple sets of properties without the limitations of single inheritance. +In addition to composition, content types can have a parent-child relationship. In the Umbraco backoffice, a content type appears underneath its parent. -Therefore, assuming that the `AboutPage` content type is a direct child of `TextPage`, it would be generated as: +By convention, a content type is always **composed of its parent** and therefore inherits its properties. However, the parent content type is treated differently, and the child content type *directly inherits* (as in C# inheritance) from the parent class. + +If `AboutPage` is a child of TextPage, its generated model would inherit directly from `TextPage`: ```csharp // Note: Inherits from TextPage @@ -108,9 +124,11 @@ public partial class AboutPage : TextPage } ``` -## Extending +## Extending Models + +Since models are partial classes, developers can extend them by adding additional properties. -Because a model is generated as a partial class, it is possible to extend it. That could be by adding a property, by dropping the following code in a `TextPage.cs` file: +For Example: ```csharp public partial class TextPage @@ -119,23 +137,23 @@ public partial class TextPage } ``` -Models builder does not take a custom partial class into account when generating the models. This means that if a custom partial class, inherits from a base class, tries to provide a constructor with the same signature, or implements a generated property, it will cause compilation errors. +Models Builder does not recognize custom partial classes during regeneration. If your custom class conflicts with the generated class (e.g., overriding a constructor), it will cause compilation errors. -Furthermore a generated model will always be instantiated with its default constructor, so if an overloading constructor is created it will never be used. +Overloaded constructors will not be used because models are always instantiated using the default constructor. -For more complex partial classes, you'll have to use the full version of the [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). +For more complex customizations, use the full version of [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). -### IModelsGenerator +### Custom Model Generation with IModelsGenerator -As of Umbraco 11.4, the IModelsGenerator interface has been added. If you want to customize how the models are generated, you can make your own implementation of the `IModelsGenerator` interface. You can then overwrite the Umbraco implementation with dependency injection. +From Umbraco 11.4, you can implement the `IModelsGenerator` interface hto customize how models are generated. This allows you to replace Umbraco’s default implementation using dependency injection: The interface can be accessed via `Infrastructure.ModelsBuilder.Building.ModelsGenerator`. -## Best Practices +## Best Practices for Extending Models Extending models should be used to add stateless, local features to models. It should not be used to transform *content* models into view models or manage trees of content. -### Example of good practice +### Good practices A customer has "posts" that has two "release date" properties. One is a true date picker property and is used to specify an actual date and to order the posts. The other is a string that is used to specify dates such as "Summer 2015" or "Q1 2016". Alongside the title of the post, the customer wants to display the text date, if present, else the actual date. If none of those are present, the Umbraco update date should be used. Keep in mind that each view can contain code to deal with the situation, but it is much more efficient to extend the `Post` model: @@ -162,7 +180,7 @@ A customer has "posts" that has two "release date" properties. One is a true dat } ``` -And to simplify the view as: +Simplified view: ```csharp
@@ -171,13 +189,12 @@ And to simplify the view as:
``` -### Example of bad practice +### Bad practices Because, by default, the content object is passed to views, one can be tempted to add view-related properties to the model. Some properties that do *not* belong to a *content* model would be: -* A `HomePage` property that would walk up the tree and return the "home page" content item -* A `Menu` property that would list the content items to display in a top menu -* Etc. +* A `HomePage` property that retrieves the "home page" content item. +* A `Menu` property that lists navigation items. Generally speaking, anything that is tied to the current request, or that depends on more than the modeled content, is a bad idea. There are much cleaner solutions, such as using true *view model* classes that would be populated by a true controller and look like: @@ -190,17 +207,17 @@ public class TextPageViewModel } ``` -One can also extend Umbraco's views to provide a special view helper that would give access to important elements of the website, so that views could contain code such as: +One can also extend Umbraco's views to provide a special view helper that gives access to important elements of the website: ```csharp @MySite.HomePage.Title ``` -### Example of ugly practice +### Ugly practices -The scope and life-cycle of a model is *not specified*. In other words, you don't know whether the model exists only for you and for the context of the current request, or if it is cached by Umbraco and shared by all requests. +The model's scope and lifecycle are *unspecified*. It may exist only for your request or be cached and shared across all requests. -As a consequence, the following code has a major issue: the `TextPage` model "caches" an instance of the `HomePageDocument` model that will never be updated if the home page is re-published. +The code has a major issue: the `TextPage` model caches a `HomePageDocument` model that will not update when the home page is re-published. ```csharp private HomePageDocument _homePage; @@ -217,4 +234,4 @@ public HomePageDocument HomePage } ``` -As a rule of thumb, models should never, *ever* reference and cache other models. +As a rule of thumb, models should never reference and cache other models.